github.com/nats-io/jwt/v2@v2.5.6/v1compat/imports.go (about)

     1  /*
     2   * Copyright 2018-2022 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package jwt
    17  
    18  import (
    19  	"io"
    20  	"net/http"
    21  	"net/url"
    22  	"time"
    23  )
    24  
    25  // Import describes a mapping from another account into this one
    26  type Import struct {
    27  	Name string `json:"name,omitempty"`
    28  	// Subject field in an import is always from the perspective of the
    29  	// initial publisher - in the case of a stream it is the account owning
    30  	// the stream (the exporter), and in the case of a service it is the
    31  	// account making the request (the importer).
    32  	Subject Subject `json:"subject,omitempty"`
    33  	Account string  `json:"account,omitempty"`
    34  	Token   string  `json:"token,omitempty"`
    35  	// To field in an import is always from the perspective of the subscriber
    36  	// in the case of a stream it is the client of the stream (the importer),
    37  	// from the perspective of a service, it is the subscription waiting for
    38  	// requests (the exporter). If the field is empty, it will default to the
    39  	// value in the Subject field.
    40  	To   Subject    `json:"to,omitempty"`
    41  	Type ExportType `json:"type,omitempty"`
    42  }
    43  
    44  // IsService returns true if the import is of type service
    45  func (i *Import) IsService() bool {
    46  	return i.Type == Service
    47  }
    48  
    49  // IsStream returns true if the import is of type stream
    50  func (i *Import) IsStream() bool {
    51  	return i.Type == Stream
    52  }
    53  
    54  // Validate checks if an import is valid for the wrapping account
    55  func (i *Import) Validate(actPubKey string, vr *ValidationResults) {
    56  	if i == nil {
    57  		vr.AddError("null import is not allowed")
    58  		return
    59  	}
    60  	if !i.IsService() && !i.IsStream() {
    61  		vr.AddError("invalid import type: %q", i.Type)
    62  	}
    63  
    64  	if i.Account == "" {
    65  		vr.AddError("account to import from is not specified")
    66  	}
    67  
    68  	i.Subject.Validate(vr)
    69  
    70  	if i.IsService() && i.Subject.HasWildCards() {
    71  		vr.AddError("services cannot have wildcard subject: %q", i.Subject)
    72  	}
    73  	if i.IsStream() && i.To.HasWildCards() {
    74  		vr.AddError("streams cannot have wildcard to subject: %q", i.Subject)
    75  	}
    76  
    77  	var act *ActivationClaims
    78  
    79  	if i.Token != "" {
    80  		// Check to see if its an embedded JWT or a URL.
    81  		if u, err := url.Parse(i.Token); err == nil && u.Scheme != "" {
    82  			c := &http.Client{Timeout: 5 * time.Second}
    83  			resp, err := c.Get(u.String())
    84  			if err != nil {
    85  				vr.AddError("import %s contains an unreachable token URL %q", i.Subject, i.Token)
    86  			}
    87  
    88  			if resp != nil {
    89  				defer resp.Body.Close()
    90  				body, err := io.ReadAll(resp.Body)
    91  				if err != nil {
    92  					vr.AddError("import %s contains an unreadable token URL %q", i.Subject, i.Token)
    93  				} else {
    94  					act, err = DecodeActivationClaims(string(body))
    95  					if err != nil {
    96  						vr.AddError("import %s contains a URL %q with an invalid activation token", i.Subject, i.Token)
    97  					}
    98  				}
    99  			}
   100  		} else {
   101  			var err error
   102  			act, err = DecodeActivationClaims(i.Token)
   103  			if err != nil {
   104  				vr.AddError("import %q contains an invalid activation token", i.Subject)
   105  			}
   106  		}
   107  	}
   108  
   109  	if act != nil {
   110  		if !(act.Issuer == i.Account || act.IssuerAccount == i.Account) {
   111  			vr.AddError("activation token doesn't match account for import %q", i.Subject)
   112  		}
   113  		if act.ClaimsData.Subject != actPubKey {
   114  			vr.AddError("activation token doesn't match account it is being included in, %q", i.Subject)
   115  		}
   116  		if act.ImportType != i.Type {
   117  			vr.AddError("mismatch between token import type %s and type of import %s", act.ImportType, i.Type)
   118  		}
   119  		act.validateWithTimeChecks(vr, false)
   120  		subj := i.Subject
   121  		if i.IsService() && i.To != "" {
   122  			subj = i.To
   123  		}
   124  		if !subj.IsContainedIn(act.ImportSubject) {
   125  			vr.AddError("activation token import subject %q doesn't match import %q", act.ImportSubject, i.Subject)
   126  		}
   127  	}
   128  }
   129  
   130  // Imports is a list of import structs
   131  type Imports []*Import
   132  
   133  // Validate checks if an import is valid for the wrapping account
   134  func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) {
   135  	toSet := make(map[Subject]bool, len(*i))
   136  	for _, v := range *i {
   137  		if v == nil {
   138  			vr.AddError("null import is not allowed")
   139  			continue
   140  		}
   141  		if v.Type == Service {
   142  			if _, ok := toSet[v.To]; ok {
   143  				vr.AddError("Duplicate To subjects for %q", v.To)
   144  			}
   145  			toSet[v.To] = true
   146  		}
   147  		v.Validate(acctPubKey, vr)
   148  	}
   149  }
   150  
   151  // Add is a simple way to add imports
   152  func (i *Imports) Add(a ...*Import) {
   153  	*i = append(*i, a...)
   154  }
   155  
   156  func (i Imports) Len() int {
   157  	return len(i)
   158  }
   159  
   160  func (i Imports) Swap(j, k int) {
   161  	i[j], i[k] = i[k], i[j]
   162  }
   163  
   164  func (i Imports) Less(j, k int) bool {
   165  	return i[j].Subject < i[k].Subject
   166  }