494 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			494 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
| package providers
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/http/httptest"
 | |
| 	"net/url"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
 | |
| 
 | |
| 	"github.com/coreos/go-oidc/v3/oidc"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 
 | |
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
 | |
| )
 | |
| 
 | |
| func newCidaasProvider(serverURL *url.URL) *CIDAASProvider {
 | |
| 	providerData := &ProviderData{
 | |
| 		ProviderName: "cidaas",
 | |
| 		ClientID:     oidcClientID,
 | |
| 		ClientSecret: oidcSecret,
 | |
| 		LoginURL: &url.URL{
 | |
| 			Scheme: serverURL.Scheme,
 | |
| 			Host:   serverURL.Host,
 | |
| 			Path:   "/login/oauth/authorize"},
 | |
| 		RedeemURL: &url.URL{
 | |
| 			Scheme: serverURL.Scheme,
 | |
| 			Host:   serverURL.Host,
 | |
| 			Path:   "/login/oauth/access_token"},
 | |
| 		ProfileURL: &url.URL{
 | |
| 			Scheme: serverURL.Scheme,
 | |
| 			Host:   serverURL.Host,
 | |
| 			Path:   "/profile"},
 | |
| 		ValidateURL: &url.URL{
 | |
| 			Scheme: serverURL.Scheme,
 | |
| 			Host:   serverURL.Host,
 | |
| 			Path:   "/api"},
 | |
| 		Scope:       "openid profile offline_access roles groups",
 | |
| 		EmailClaim:  "email",
 | |
| 		GroupsClaim: "groups",
 | |
| 		Verifier: oidc.NewVerifier(
 | |
| 			oidcIssuer,
 | |
| 			mockJWKS{},
 | |
| 			&oidc.Config{ClientID: oidcClientID},
 | |
| 		),
 | |
| 	}
 | |
| 	cfg := options.Provider{
 | |
| 		Type: options.CidaasProvider,
 | |
| 	}
 | |
| 
 | |
| 	p := NewCIDAASProvider(providerData, cfg)
 | |
| 
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func newCidaasServer(pathBodyMap map[string][]byte) (*url.URL, *httptest.Server) {
 | |
| 	s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 | |
| 		body, ok := pathBodyMap[r.URL.Path]
 | |
| 		if !ok {
 | |
| 			rw.WriteHeader(404)
 | |
| 			return
 | |
| 		}
 | |
| 		rw.Header().Add("content-type", "application/json")
 | |
| 		_, _ = rw.Write(body)
 | |
| 	}))
 | |
| 	u, _ := url.Parse(s.URL)
 | |
| 	return u, s
 | |
| }
 | |
| 
 | |
| func newTestCidaasSetup(pathToBodyMap map[string][]byte) (*httptest.Server, *CIDAASProvider) {
 | |
| 	redeemURL, server := newCidaasServer(pathToBodyMap)
 | |
| 	provider := newCidaasProvider(redeemURL)
 | |
| 	return server, provider
 | |
| }
 | |
| 
 | |
| func TestCidaasProvider_EnrichSession(t *testing.T) {
 | |
| 	testCases := map[string]struct {
 | |
| 		ExistingSession *sessions.SessionState
 | |
| 		EmailClaim      string
 | |
| 		GroupsClaim     string
 | |
| 		ProfileJSON     map[string]interface{}
 | |
| 		ExpectedError   error
 | |
| 		ExpectedSession *sessions.SessionState
 | |
| 	}{
 | |
| 		"Missing Email Only in Profile URL": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "missing.email",
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"email": "found@email.com",
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "missing.email",
 | |
| 				Email:        "found@email.com",
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Missing Email with Custom Claim": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "missing.email",
 | |
| 				Groups:       []string{"already", "populated"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "weird",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"weird": "weird@claim.com",
 | |
| 				"groups": []map[string]interface{}{
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "missing.email",
 | |
| 				Email:        "weird@claim.com",
 | |
| 				Groups:       []string{"CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Missing Email not in Profile URL": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "missing.email",
 | |
| 				Groups:       []string{"already", "populated"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"groups": []map[string]interface{}{
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: errors.New("neither the id_token nor the profileURL set an email"),
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "missing.email",
 | |
| 				Groups:       []string{"CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Missing Groups": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       nil,
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"email": "new@thing.com",
 | |
| 				"groups": []map[string]interface{}{
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       []string{"CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Empty Groups Claims": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       []string{},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"email": "new@thing.com",
 | |
| 				"groups": []map[string]interface{}{
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       []string{"CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Missing Groups with Custom Claim": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       nil,
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups2",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"email": "already@populated.com",
 | |
| 				"groups2": []map[string]interface{}{
 | |
| 					{
 | |
| 						"sub":     "aa5181ea-0841-4ea7-b67f-81882f153d40",
 | |
| 						"groupId": "CIDAAS_ADMINS",
 | |
| 						"path":    "/CIDAAS_ADMINS/",
 | |
| 						"roles":   []string{"ADMIN"},
 | |
| 					},
 | |
| 					{
 | |
| 						"sub":       "aa5181ea-0841-4ea7-b67f-81882f153d39",
 | |
| 						"groupId":   "customers",
 | |
| 						"groupType": "Customers",
 | |
| 						"path":      "/customers/",
 | |
| 						"roles": []string{
 | |
| 							"CUSTOMER_ACCOUNT_LOGIN",
 | |
| 							"GROUP_ADMIN",
 | |
| 						},
 | |
| 					},
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       []string{"CIDAAS_ADMINS:ADMIN", "customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Just format Groups": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       nil,
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups2",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"email": "already@populated.com",
 | |
| 				"groups2": []map[string]interface{}{
 | |
| 					{
 | |
| 						"sub":       "aa5181ea-0841-4ea7-b67f-81882f153d39",
 | |
| 						"groupId":   "customers",
 | |
| 						"groupType": "Customers",
 | |
| 						"path":      "/customers/",
 | |
| 						"roles": []string{
 | |
| 							"CUSTOMER_ACCOUNT_LOGIN",
 | |
| 							"GROUP_ADMIN",
 | |
| 						},
 | |
| 					},
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       []string{"customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Missing Groups String Profile URL Response": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       nil,
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"groups": []map[string]interface{}{
 | |
| 					{
 | |
| 						"sub":     "aa5181ea-0841-4ea7-b67f-81882f153d40",
 | |
| 						"groupId": "CIDAAS_ADMINS",
 | |
| 						"path":    "/CIDAAS_ADMINS/",
 | |
| 						"roles":   []string{"ADMIN"},
 | |
| 					},
 | |
| 					{
 | |
| 						"sub":       "aa5181ea-0841-4ea7-b67f-81882f153d39",
 | |
| 						"groupId":   "customers",
 | |
| 						"groupType": "Customers",
 | |
| 						"path":      "/customers/",
 | |
| 						"roles": []string{
 | |
| 							"CUSTOMER_ACCOUNT_LOGIN",
 | |
| 							"GROUP_ADMIN",
 | |
| 						},
 | |
| 					},
 | |
| 					{
 | |
| 						"groupId": "CIDAAS_USERS",
 | |
| 						"roles":   []string{"USER"},
 | |
| 					},
 | |
| 				},
 | |
| 				"roles": []string{"USER"},
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				Groups:       []string{"CIDAAS_ADMINS:ADMIN", "customers:CUSTOMER_ACCOUNT_LOGIN", "customers:GROUP_ADMIN", "CIDAAS_USERS:USER", "cidaas:USER"},
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 		"Missing Groups in both Claims and Profile URL": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			EmailClaim:  "email",
 | |
| 			GroupsClaim: "groups",
 | |
| 			ProfileJSON: map[string]interface{}{
 | |
| 				"email": "new@thing.com",
 | |
| 			},
 | |
| 			ExpectedError: nil,
 | |
| 			ExpectedSession: &sessions.SessionState{
 | |
| 				User:         "already",
 | |
| 				Email:        "already@populated.com",
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	for testName, tc := range testCases {
 | |
| 		t.Run(testName, func(t *testing.T) {
 | |
| 			path := "/userinfo/"
 | |
| 			jsonResp, err := json.Marshal(tc.ProfileJSON)
 | |
| 			assert.NoError(t, err)
 | |
| 
 | |
| 			server, provider := newTestCidaasSetup(map[string][]byte{path: jsonResp})
 | |
| 			provider.ProfileURL, err = url.Parse(fmt.Sprintf("%s%s", server.URL, path))
 | |
| 			assert.NoError(t, err)
 | |
| 
 | |
| 			provider.EmailClaim = tc.EmailClaim
 | |
| 			provider.GroupsClaim = tc.GroupsClaim
 | |
| 			defer server.Close()
 | |
| 
 | |
| 			err = provider.EnrichSession(context.Background(), tc.ExistingSession)
 | |
| 			if tc.ExpectedError != nil {
 | |
| 				assert.EqualError(t, err, tc.ExpectedError.Error())
 | |
| 			} else {
 | |
| 				assert.NoError(t, err)
 | |
| 			}
 | |
| 			assert.Equal(t, *tc.ExpectedSession, *tc.ExistingSession)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCidaasProvider_RefreshSession(t *testing.T) {
 | |
| 	testCases := map[string]struct {
 | |
| 		ExistingSession   *sessions.SessionState
 | |
| 		EmailClaim        string
 | |
| 		GroupsClaim       string
 | |
| 		ProfileJSON       map[string]interface{}
 | |
| 		RedeemJSON        redeemTokenResponse
 | |
| 		ExpectedRefreshed bool
 | |
| 		ExpectedError     error
 | |
| 		ExpectedEmail     string
 | |
| 		ExpectedUser      string
 | |
| 	}{
 | |
| 		"Refresh session successfully": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "session.is.not.locked",
 | |
| 				Email:        "found@email.com",
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			RedeemJSON: redeemTokenResponse{
 | |
| 				AccessToken:  accessToken,
 | |
| 				ExpiresIn:    10,
 | |
| 				TokenType:    "Bearer",
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			ExpectedRefreshed: true,
 | |
| 			ExpectedError:     nil,
 | |
| 			ExpectedEmail:     defaultIDToken.Email,
 | |
| 			ExpectedUser:      defaultIDToken.Subject,
 | |
| 		},
 | |
| 		"Unable to refresh session": {
 | |
| 			ExistingSession: &sessions.SessionState{
 | |
| 				User:         "session.is.unable.to.refresh",
 | |
| 				Email:        "found@email.com",
 | |
| 				IDToken:      idToken,
 | |
| 				AccessToken:  accessToken,
 | |
| 				RefreshToken: refreshToken,
 | |
| 			},
 | |
| 			ExpectedRefreshed: false,
 | |
| 			ExpectedError:     fmt.Errorf("unable to redeem refresh token: failed to get token: oauth2: server response missing access_token"),
 | |
| 			ExpectedUser:      "session.is.unable.to.refresh",
 | |
| 			ExpectedEmail:     "found@email.com",
 | |
| 		},
 | |
| 	}
 | |
| 	for testName, tc := range testCases {
 | |
| 		t.Run(testName, func(t *testing.T) {
 | |
| 			idToken, _ := newSignedTestIDToken(defaultIDToken)
 | |
| 			tc.RedeemJSON.IDToken = idToken
 | |
| 			redeemPath := "/token/"
 | |
| 			redeemJSONResp, err := json.Marshal(tc.RedeemJSON)
 | |
| 			assert.NoError(t, err)
 | |
| 
 | |
| 			serverURL, server := newCidaasServer(
 | |
| 				map[string][]byte{
 | |
| 					redeemPath: redeemJSONResp,
 | |
| 				})
 | |
| 			provider := newCidaasProvider(serverURL)
 | |
| 
 | |
| 			// Disable session enrichment, because we want to focus on refreshing logic
 | |
| 			provider.ProfileURL, err = url.Parse("")
 | |
| 			assert.NoError(t, err)
 | |
| 			provider.RedeemURL, err = url.Parse(fmt.Sprintf("%s%s", server.URL, redeemPath))
 | |
| 			assert.NoError(t, err)
 | |
| 
 | |
| 			provider.GroupsClaim = tc.GroupsClaim
 | |
| 			defer server.Close()
 | |
| 
 | |
| 			var refreshed bool
 | |
| 			refreshed, err = provider.RefreshSession(context.Background(), tc.ExistingSession)
 | |
| 
 | |
| 			if tc.ExpectedError != nil {
 | |
| 				assert.EqualError(t, err, tc.ExpectedError.Error())
 | |
| 			} else {
 | |
| 				assert.NoError(t, err)
 | |
| 			}
 | |
| 			assert.Equal(t, tc.ExpectedRefreshed, refreshed)
 | |
| 			assert.Equal(t, tc.ExpectedEmail, tc.ExistingSession.Email)
 | |
| 			assert.Equal(t, tc.ExpectedUser, tc.ExistingSession.User)
 | |
| 		})
 | |
| 	}
 | |
| }
 |