github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/mw_openid.go (about) 1 package gateway 2 3 import ( 4 "crypto/md5" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 "net/http" 9 "sync" 10 11 jwt "github.com/dgrijalva/jwt-go" 12 "github.com/sirupsen/logrus" 13 14 "github.com/TykTechnologies/openid2go/openid" 15 "github.com/TykTechnologies/tyk/apidef" 16 "github.com/TykTechnologies/tyk/user" 17 ) 18 19 const OIDPREFIX = "openid" 20 21 type OpenIDMW struct { 22 BaseMiddleware 23 providerConfiguration *openid.Configuration 24 provider_client_policymap map[string]map[string]string 25 lock sync.RWMutex 26 } 27 28 func (k *OpenIDMW) Name() string { 29 return "OpenIDMW" 30 } 31 32 func (k *OpenIDMW) EnabledForSpec() bool { 33 return k.Spec.UseOpenID 34 } 35 36 func (k *OpenIDMW) Init() { 37 k.provider_client_policymap = make(map[string]map[string]string) 38 // Create an OpenID Configuration and store 39 var err error 40 k.providerConfiguration, err = openid.NewConfiguration(openid.ProvidersGetter(k.getProviders), 41 openid.ErrorHandler(k.dummyErrorHandler)) 42 43 if err != nil { 44 k.Logger().WithError(err).Error("OpenID configuration error") 45 } 46 } 47 48 func (k *OpenIDMW) getProviders() ([]openid.Provider, error) { 49 providers := []openid.Provider{} 50 k.Logger().Debug("Setting up providers: ", k.Spec.OpenIDOptions.Providers) 51 for _, provider := range k.Spec.OpenIDOptions.Providers { 52 iss := provider.Issuer 53 k.Logger().Debug("Setting up Issuer: ", iss) 54 providerClientArray := make([]string, len(provider.ClientIDs)) 55 56 i := 0 57 for clientID, policyID := range provider.ClientIDs { 58 clID, _ := base64.StdEncoding.DecodeString(clientID) 59 clientID := string(clID) 60 61 k.lock.Lock() 62 if k.provider_client_policymap[iss] == nil { 63 k.provider_client_policymap[iss] = map[string]string{clientID: policyID} 64 } else { 65 k.provider_client_policymap[iss][clientID] = policyID 66 } 67 k.lock.Unlock() 68 69 k.Logger().Debug("--> Setting up client: ", clientID, " with policy: ", policyID) 70 providerClientArray[i] = clientID 71 i++ 72 } 73 74 p, err := openid.NewProvider(iss, providerClientArray) 75 76 if err != nil { 77 k.Logger().WithError(err).WithFields(logrus.Fields{ 78 "provider": iss, 79 }).Error("Failed to create provider") 80 } else { 81 providers = append(providers, p) 82 } 83 } 84 85 return providers, nil 86 } 87 88 // We don't want any of the error handling, we use our own 89 func (k *OpenIDMW) dummyErrorHandler(e error, w http.ResponseWriter, r *http.Request) bool { 90 k.Logger().WithError(e).Warning("JWT Invalid") 91 return true 92 } 93 94 func (k *OpenIDMW) getAuthType() string { 95 return oidcType 96 } 97 98 func (k *OpenIDMW) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) { 99 if ctxGetRequestStatus(r) == StatusOkAndIgnore { 100 return nil, http.StatusOK 101 } 102 103 k.providerConfiguration.IDTokenGetter = func(r *http.Request) (token string, err error) { 104 token, _ = k.getAuthToken(k.getAuthType(), r) 105 return openid.CheckAndSplitHeader(token) 106 } 107 108 logger := k.Logger() 109 // 1. Validate the JWT 110 ouser, token, halt := openid.AuthenticateOIDWithUser(k.providerConfiguration, w, r) 111 112 // 2. Generate the internal representation for the key 113 if halt { 114 // Fire Authfailed Event 115 k.reportLoginFailure("[JWT]", r) 116 return errors.New("Key not authorised"), http.StatusUnauthorized 117 } 118 119 // 3. Create or set the session to match 120 iss, found := token.Claims.(jwt.MapClaims)["iss"] 121 clients, cfound := token.Claims.(jwt.MapClaims)["aud"] 122 123 if !found && !cfound { 124 logger.Error("No issuer or audiences found!") 125 k.reportLoginFailure("[NOT GENERATED]", r) 126 return errors.New("Key not authorised"), http.StatusUnauthorized 127 } 128 129 // decide if we use policy ID from provider client settings or list of policies from scope-policy mapping 130 useScope := len(k.Spec.JWTScopeToPolicyMapping) != 0 131 132 k.lock.RLock() 133 clientSet, foundIssuer := k.provider_client_policymap[iss.(string)] 134 k.lock.RUnlock() 135 if !foundIssuer { 136 logger.Error("No issuer or audiences found!") 137 k.reportLoginFailure("[NOT GENERATED]", r) 138 return errors.New("Key not authorised"), http.StatusUnauthorized 139 } 140 141 policyID := "" 142 clientID := "" 143 switch v := clients.(type) { 144 case string: 145 k.lock.RLock() 146 policyID = clientSet[v] 147 k.lock.RUnlock() 148 clientID = v 149 case []interface{}: 150 for _, audVal := range v { 151 k.lock.RLock() 152 policy, foundPolicy := clientSet[audVal.(string)] 153 k.lock.RUnlock() 154 if foundPolicy { 155 clientID = audVal.(string) 156 policyID = policy 157 break 158 } 159 } 160 } 161 162 if !useScope && policyID == "" { 163 logger.Error("No matching policy found!") 164 k.reportLoginFailure("[NOT GENERATED]", r) 165 return errors.New("Key not authorised"), http.StatusUnauthorized 166 } 167 168 data := []byte(ouser.ID) 169 keyID := fmt.Sprintf("%x", md5.Sum(data)) 170 sessionID := generateToken(k.Spec.OrgID, keyID) 171 172 if k.Spec.OpenIDOptions.SegregateByClient { 173 // We are segregating by client, so use it as part of the internal token 174 logger.Debug("Client ID:", clientID) 175 sessionID = generateToken(k.Spec.OrgID, fmt.Sprintf("%x", md5.Sum([]byte(clientID)))+keyID) 176 } 177 178 logger.Debug("Generated Session ID: ", sessionID) 179 180 var policiesToApply []string 181 if !useScope { 182 policiesToApply = append(policiesToApply, policyID) 183 } else { 184 scopeClaimName := k.Spec.JWTScopeClaimName 185 if scopeClaimName == "" { 186 scopeClaimName = "scope" 187 } 188 189 if scope := getScopeFromClaim(token.Claims.(jwt.MapClaims), scopeClaimName); scope != nil { 190 // add all policies matched from scope-policy mapping 191 policiesToApply = mapScopeToPolicies(k.Spec.JWTScopeToPolicyMapping, scope) 192 } 193 } 194 195 session, exists := k.CheckSessionAndIdentityForValidKey(&sessionID, r) 196 if !exists { 197 // Create it 198 logger.Debug("Key does not exist, creating") 199 session = user.SessionState{Mutex: &sync.RWMutex{}} 200 201 if !useScope { 202 // We need a base policy as a template, either get it from the token itself OR a proxy client ID within Tyk 203 newSession, err := generateSessionFromPolicy(policyID, 204 k.Spec.OrgID, 205 true) 206 207 if err != nil { 208 k.reportLoginFailure(sessionID, r) 209 logger.Error("Could not find a valid policy to apply to this token!") 210 return errors.New("Key not authorized: no matching policy"), http.StatusForbidden 211 } 212 213 session = newSession 214 } 215 216 session.OrgID = k.Spec.OrgID 217 session.SetMetaData(map[string]interface{}{"TykJWTSessionID": sessionID, "ClientID": clientID}) 218 session.Alias = clientID + ":" + ouser.ID 219 220 // Update the session in the session manager in case it gets called again 221 logger.Debug("Policy applied to key") 222 } 223 // apply new policy to session if any and update session 224 session.SetPolicies(policiesToApply...) 225 if err := k.ApplyPolicies(&session); err != nil { 226 k.Logger().WithError(err).Error("Could not apply new policy from OIDC client to session") 227 return errors.New("Key not authorized: could not apply new policy"), http.StatusForbidden 228 } 229 230 // 4. Set session state on context, we will need it later 231 switch k.Spec.BaseIdentityProvidedBy { 232 case apidef.OIDCUser, apidef.UnsetAuth: 233 ctxSetSession(r, &session, sessionID, true) 234 } 235 ctxSetJWTContextVars(k.Spec, r, token) 236 237 return nil, http.StatusOK 238 } 239 240 func (k *OpenIDMW) reportLoginFailure(tykId string, r *http.Request) { 241 k.Logger().WithFields(logrus.Fields{ 242 "key": obfuscateKey(tykId), 243 }).Warning("Attempted access with invalid key.") 244 245 // Fire Authfailed Event 246 AuthFailed(k, r, tykId) 247 248 // Report in health check 249 reportHealthValue(k.Spec, KeyFailure, "1") 250 }