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 }