github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+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  	"github.com/sirupsen/logrus"
    12  	jwt "github.com/dgrijalva/jwt-go"
    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) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
    95  	logger := k.Logger()
    96  	// 1. Validate the JWT
    97  	ouser, token, halt := openid.AuthenticateOIDWithUser(k.providerConfiguration, w, r)
    98  
    99  	// 2. Generate the internal representation for the key
   100  	if halt {
   101  		// Fire Authfailed Event
   102  		k.reportLoginFailure("[JWT]", r)
   103  		return errors.New("Key not authorised"), http.StatusUnauthorized
   104  	}
   105  
   106  	// 3. Create or set the session to match
   107  	iss, found := token.Claims.(jwt.MapClaims)["iss"]
   108  	clients, cfound := token.Claims.(jwt.MapClaims)["aud"]
   109  
   110  	if !found && !cfound {
   111  		logger.Error("No issuer or audiences found!")
   112  		k.reportLoginFailure("[NOT GENERATED]", r)
   113  		return errors.New("Key not authorised"), http.StatusUnauthorized
   114  	}
   115  
   116  	// decide if we use policy ID from provider client settings or list of policies from scope-policy mapping
   117  	useScope := len(k.Spec.JWTScopeToPolicyMapping) != 0
   118  
   119  	k.lock.RLock()
   120  	clientSet, foundIssuer := k.provider_client_policymap[iss.(string)]
   121  	k.lock.RUnlock()
   122  	if !foundIssuer {
   123  		logger.Error("No issuer or audiences found!")
   124  		k.reportLoginFailure("[NOT GENERATED]", r)
   125  		return errors.New("Key not authorised"), http.StatusUnauthorized
   126  	}
   127  
   128  	policyID := ""
   129  	clientID := ""
   130  	switch v := clients.(type) {
   131  	case string:
   132  		k.lock.RLock()
   133  		policyID = clientSet[v]
   134  		k.lock.RUnlock()
   135  		clientID = v
   136  	case []interface{}:
   137  		for _, audVal := range v {
   138  			k.lock.RLock()
   139  			policy, foundPolicy := clientSet[audVal.(string)]
   140  			k.lock.RUnlock()
   141  			if foundPolicy {
   142  				clientID = audVal.(string)
   143  				policyID = policy
   144  				break
   145  			}
   146  		}
   147  	}
   148  
   149  	if !useScope && policyID == "" {
   150  		logger.Error("No matching policy found!")
   151  		k.reportLoginFailure("[NOT GENERATED]", r)
   152  		return errors.New("Key not authorised"), http.StatusUnauthorized
   153  	}
   154  
   155  	data := []byte(ouser.ID)
   156  	keyID := fmt.Sprintf("%x", md5.Sum(data))
   157  	sessionID := generateToken(k.Spec.OrgID, keyID)
   158  
   159  	if k.Spec.OpenIDOptions.SegregateByClient {
   160  		// We are segregating by client, so use it as part of the internal token
   161  		logger.Debug("Client ID:", clientID)
   162  		sessionID = generateToken(k.Spec.OrgID, fmt.Sprintf("%x", md5.Sum([]byte(clientID)))+keyID)
   163  	}
   164  
   165  	logger.Debug("Generated Session ID: ", sessionID)
   166  
   167  	var policiesToApply []string
   168  	if !useScope {
   169  		policiesToApply = append(policiesToApply, policyID)
   170  	} else {
   171  		scopeClaimName := k.Spec.JWTScopeClaimName
   172  		if scopeClaimName == "" {
   173  			scopeClaimName = "scope"
   174  		}
   175  
   176  		if scope := getScopeFromClaim(token.Claims.(jwt.MapClaims), scopeClaimName); scope != nil {
   177  			// add all policies matched from scope-policy mapping
   178  			policiesToApply = mapScopeToPolicies(k.Spec.JWTScopeToPolicyMapping, scope)
   179  		}
   180  	}
   181  
   182  	session, exists := k.CheckSessionAndIdentityForValidKey(sessionID, r)
   183  	if !exists {
   184  		// Create it
   185  		logger.Debug("Key does not exist, creating")
   186  		session = user.SessionState{}
   187  
   188  		if !useScope {
   189  			// We need a base policy as a template, either get it from the token itself OR a proxy client ID within Tyk
   190  			newSession, err := generateSessionFromPolicy(policyID,
   191  				k.Spec.OrgID,
   192  				true)
   193  
   194  			if err != nil {
   195  				k.reportLoginFailure(sessionID, r)
   196  				logger.Error("Could not find a valid policy to apply to this token!")
   197  				return errors.New("Key not authorized: no matching policy"), http.StatusForbidden
   198  			}
   199  
   200  			session = newSession
   201  		}
   202  
   203  		session.OrgID = k.Spec.OrgID
   204  		session.MetaData = map[string]interface{}{"TykJWTSessionID": sessionID, "ClientID": clientID}
   205  		session.Alias = clientID + ":" + ouser.ID
   206  
   207  		// Update the session in the session manager in case it gets called again
   208  		logger.Debug("Policy applied to key")
   209  	}
   210  	// apply new policy to session if any and update session
   211  	session.SetPolicies(policiesToApply...)
   212  	if err := k.ApplyPolicies(&session); err != nil {
   213  		k.Logger().WithError(err).Error("Could not apply new policy from OIDC client to session")
   214  		return errors.New("Key not authorized: could not apply new policy"), http.StatusForbidden
   215  	}
   216  
   217  	// 4. Set session state on context, we will need it later
   218  	switch k.Spec.BaseIdentityProvidedBy {
   219  	case apidef.OIDCUser, apidef.UnsetAuth:
   220  		ctxSetSession(r, &session, sessionID, true)
   221  	}
   222  	ctxSetJWTContextVars(k.Spec, r, token)
   223  
   224  	return nil, http.StatusOK
   225  }
   226  
   227  func (k *OpenIDMW) reportLoginFailure(tykId string, r *http.Request) {
   228  	k.Logger().WithFields(logrus.Fields{
   229  		"key": obfuscateKey(tykId),
   230  	}).Warning("Attempted access with invalid key.")
   231  
   232  	// Fire Authfailed Event
   233  	AuthFailed(k, r, tykId)
   234  
   235  	// Report in health check
   236  	reportHealthValue(k.Spec, KeyFailure, "1")
   237  }