github.com/greenpau/go-authcrunch@v1.1.4/pkg/authz/validator/sources.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     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  package validator
    16  
    17  import (
    18  	"context"
    19  	"github.com/greenpau/go-authcrunch/pkg/errors"
    20  	"github.com/greenpau/go-authcrunch/pkg/requests"
    21  	"github.com/greenpau/go-authcrunch/pkg/user"
    22  	"net/http"
    23  	"strings"
    24  )
    25  
    26  const (
    27  	tokenSourceHeader = "header"
    28  	tokenSourceCookie = "cookie"
    29  	tokenSourceQuery  = "query"
    30  )
    31  
    32  var (
    33  	defaultTokenNames = []string{
    34  		"access_token",
    35  		"jwt_access_token",
    36  	}
    37  	defaultTokenSourcePriority = map[string]int{
    38  		tokenSourceCookie: 0,
    39  		tokenSourceHeader: 1,
    40  		tokenSourceQuery:  2,
    41  	}
    42  	defaultTokenSources []string
    43  )
    44  
    45  func init() {
    46  	defaultTokenSources = make([]string, len(defaultTokenSourcePriority))
    47  	for source, priority := range defaultTokenSourcePriority {
    48  		defaultTokenSources[priority] = source
    49  	}
    50  }
    51  
    52  func (v *TokenValidator) clearAuthSources() {
    53  	v.clearAuthHeaders()
    54  	v.clearAuthCookies()
    55  	v.clearAuthQueryParams()
    56  }
    57  
    58  // clearAuthQueryParams clears source HTTP query parameters.
    59  func (v *TokenValidator) clearAuthQueryParams() {
    60  	v.authQueryParams = make(map[string]interface{})
    61  }
    62  
    63  // clearAuthHeaders clears source HTTP Authorization header.
    64  func (v *TokenValidator) clearAuthHeaders() {
    65  	v.authHeaders = make(map[string]interface{})
    66  }
    67  
    68  // clearAuthCookies clears source HTTP cookies.
    69  func (v *TokenValidator) clearAuthCookies() {
    70  	v.authCookies = make(map[string]interface{})
    71  }
    72  
    73  // parseQueryParams authorizes HTTP requests based on the presence and the
    74  // content of the tokens in HTTP query parameters.
    75  func (v *TokenValidator) parseQueryParams(ctx context.Context, r *http.Request, ar *requests.AuthorizationRequest) {
    76  	values := r.URL.Query()
    77  	if len(values) == 0 {
    78  		return
    79  	}
    80  	for k := range v.authQueryParams {
    81  		value := values.Get(k)
    82  		if len(value) > 32 {
    83  			ar.Token.Found = true
    84  			ar.Token.Name = k
    85  			ar.Token.Payload = value
    86  			ar.Token.Source = tokenSourceQuery
    87  			return
    88  		}
    89  	}
    90  	return
    91  }
    92  
    93  // AuthorizeAuthorizationHeader authorizes HTTP requests based on the presence and the
    94  // content of the tokens in HTTP Authorization header.
    95  func (v *TokenValidator) parseAuthHeader(ctx context.Context, r *http.Request, ar *requests.AuthorizationRequest) {
    96  	hdr := r.Header.Get("Authorization")
    97  	if hdr == "" {
    98  		return
    99  	}
   100  	entries := strings.Split(hdr, ",")
   101  	for _, entry := range entries {
   102  		if v.opts.ValidateBearerHeader && strings.HasPrefix(entry, "Bearer") {
   103  			// If JWT token as being passed as a bearer token
   104  			// then, the token will not be a key-value pair.
   105  			kv := strings.SplitN(entry, " ", 2)
   106  			if len(kv) != 2 {
   107  				continue
   108  			}
   109  			ar.Token.Found = true
   110  			ar.Token.Name = "bearer"
   111  			ar.Token.Payload = strings.TrimSpace(kv[1])
   112  			ar.Token.Source = tokenSourceHeader
   113  			return
   114  		}
   115  		kv := strings.SplitN(entry, "=", 2)
   116  		if len(kv) != 2 {
   117  			continue
   118  		}
   119  		k := strings.TrimSpace(kv[0])
   120  		if _, exists := v.authHeaders[k]; exists {
   121  			ar.Token.Found = true
   122  			ar.Token.Name = k
   123  			ar.Token.Payload = strings.TrimSpace(kv[1])
   124  			ar.Token.Source = tokenSourceHeader
   125  			return
   126  		}
   127  	}
   128  	return
   129  }
   130  
   131  // AuthorizeCookies authorizes HTTP requests based on the presence and the
   132  // content of the tokens in HTTP cookies.
   133  func (v *TokenValidator) parseCookies(ctx context.Context, r *http.Request, ar *requests.AuthorizationRequest) {
   134  	for _, cookie := range r.Cookies() {
   135  		if _, exists := v.authCookies[cookie.Name]; !exists {
   136  			continue
   137  		}
   138  		if len(cookie.Value) < 32 {
   139  			continue
   140  		}
   141  		parts := strings.Split(strings.TrimSpace(cookie.Value), " ")
   142  		ar.Token.Found = true
   143  		ar.Token.Name = cookie.Name
   144  		ar.Token.Payload = strings.TrimSpace(parts[0])
   145  		ar.Token.Source = tokenSourceCookie
   146  		return
   147  	}
   148  	return
   149  }
   150  
   151  // Authorize authorizes HTTP requests based on the presence and the content of
   152  // the tokens in the requests.
   153  func (v *TokenValidator) Authorize(ctx context.Context, r *http.Request, ar *requests.AuthorizationRequest) (usr *user.User, err error) {
   154  	// var token, tokenName, tokenSource string
   155  	// var found bool
   156  	for _, sourceName := range v.tokenSources {
   157  		switch sourceName {
   158  		case tokenSourceHeader:
   159  			v.parseAuthHeader(ctx, r, ar)
   160  		case tokenSourceCookie:
   161  			v.parseCookies(ctx, r, ar)
   162  		case tokenSourceQuery:
   163  			v.parseQueryParams(ctx, r, ar)
   164  		}
   165  		if ar.Token.Found {
   166  			break
   167  		}
   168  	}
   169  
   170  	if !ar.Token.Found && v.customAuthEnabled {
   171  		// Search for credentials (basic, api key, etc.) in HTTP headers.
   172  		if err := v.parseCustomAuthHeader(ctx, r, ar); err != nil {
   173  			return nil, err
   174  		}
   175  	}
   176  
   177  	if !ar.Token.Found {
   178  		return nil, errors.ErrNoTokenFound
   179  	}
   180  
   181  	// Perform cache lookup for the previously obtained credentials.
   182  	usr = v.cache.Get(ar.Token.Payload)
   183  	if usr == nil {
   184  		// The user is not in the cache.
   185  		usr, err = v.keystore.ParseToken(ar)
   186  		if err != nil {
   187  			return nil, err
   188  		}
   189  	}
   190  
   191  	if err := v.guardian.authorize(ctx, r, usr); err != nil {
   192  		ar.Response.User = make(map[string]interface{})
   193  		if usr.Claims.ID != "" {
   194  			ar.Response.User["jti"] = usr.Claims.ID
   195  		}
   196  		if usr.Claims.Subject != "" {
   197  			ar.Response.User["sub"] = usr.Claims.Subject
   198  		}
   199  		if usr.Claims.Email != "" {
   200  			ar.Response.User["email"] = usr.Claims.Email
   201  		}
   202  		if usr.Claims.Name != "" {
   203  			ar.Response.User["name"] = usr.Claims.Name
   204  		}
   205  		return usr, err
   206  	}
   207  	usr.TokenSource = ar.Token.Source
   208  	usr.TokenName = ar.Token.Name
   209  	usr.Token = ar.Token.Payload
   210  	return usr, nil
   211  }