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  }