Merge pull request #262 from vmware-tanzu/token-refresh

Support for the refresh grant in the supervisor's token endpoint
This commit is contained in:
Ryan Richard 2020-12-09 15:22:02 -08:00 committed by GitHub
commit 4d82ec1283
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1053 additions and 388 deletions

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"time" "time"
coreosoidc "github.com/coreos/go-oidc"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid" "github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt" "github.com/ory/fosite/token/jwt"
@ -57,7 +58,10 @@ func NewHandler(
} }
// Grant the openid scope (for now) if they asked for it so that `NewAuthorizeResponse` will perform its OIDC validations. // Grant the openid scope (for now) if they asked for it so that `NewAuthorizeResponse` will perform its OIDC validations.
grantOpenIDScopeIfRequested(authorizeRequester) oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOpenID)
// There don't seem to be any validations inside `NewAuthorizeResponse` related to the offline_access scope
// at this time, however we will temporarily grant the scope just in case that changes in a future release of fosite.
oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOfflineAccess)
now := time.Now() now := time.Now()
_, err = oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &openid.DefaultSession{ _, err = oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &openid.DefaultSession{
@ -148,14 +152,6 @@ func readCSRFCookie(r *http.Request, codec oidc.Codec) csrftoken.CSRFToken {
return csrfFromCookie return csrfFromCookie
} }
func grantOpenIDScopeIfRequested(authorizeRequester fosite.AuthorizeRequester) {
for _, scope := range authorizeRequester.GetRequestedScopes() {
if scope == "openid" {
authorizeRequester.GrantScope(scope)
}
}
}
func chooseUpstreamIDP(idpListGetter oidc.IDPListGetter) (provider.UpstreamOIDCIdentityProviderI, error) { func chooseUpstreamIDP(idpListGetter oidc.IDPListGetter) (provider.UpstreamOIDCIdentityProviderI, error) {
allUpstreamIDPs := idpListGetter.GetIDPList() allUpstreamIDPs := idpListGetter.GetIDPList()
if len(allUpstreamIDPs) == 0 { if len(allUpstreamIDPs) == 0 {

View File

@ -119,7 +119,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
Name: "some-idp", Name: "some-idp",
ClientID: "some-client-id", ClientID: "some-client-id",
AuthorizationURL: *upstreamAuthURL, AuthorizationURL: *upstreamAuthURL,
Scopes: []string{"scope1", "scope2"}, Scopes: []string{"scope1", "scope2"}, // the scopes to request when starting the upstream authorization flow
} }
// Configure fosite the same way that the production code would, using NullStorage to turn off storage. // Configure fosite the same way that the production code would, using NullStorage to turn off storage.
@ -372,6 +372,26 @@ func TestAuthorizationEndpoint(t *testing.T) {
wantUpstreamStateParamInLocationHeader: true, wantUpstreamStateParamInLocationHeader: true,
wantBodyStringWithLocationInHref: true, wantBodyStringWithLocationInHref: true,
}, },
{
name: "happy path when downstream requested scopes include offline_access",
issuer: downstreamIssuer,
idpListGetter: oidctestutil.NewIDPListGetter(&upstreamOIDCIdentityProvider),
generateCSRF: happyCSRFGenerator,
generatePKCE: happyPKCEGenerator,
generateNonce: happyNonceGenerator,
stateEncoder: happyStateEncoder,
cookieEncoder: happyCookieEncoder,
method: http.MethodGet,
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid offline_access"}),
wantStatus: http.StatusFound,
wantContentType: "text/html; charset=utf-8",
wantCSRFValueInCookieHeader: happyCSRF,
wantLocationHeader: expectedRedirectLocation(expectedUpstreamStateParam(map[string]string{
"scope": "openid offline_access",
}, "", "")),
wantUpstreamStateParamInLocationHeader: true,
wantBodyStringWithLocationInHref: true,
},
{ {
name: "downstream redirect uri does not match what is configured for client", name: "downstream redirect uri does not match what is configured for client",
issuer: downstreamIssuer, issuer: downstreamIssuer,

View File

@ -11,6 +11,7 @@ import (
"net/url" "net/url"
"time" "time"
coreosoidc "github.com/coreos/go-oidc"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/handler/openid" "github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt" "github.com/ory/fosite/token/jwt"
@ -70,8 +71,9 @@ func NewHandler(
return httperr.New(http.StatusBadRequest, "error using state downstream auth params") return httperr.New(http.StatusBadRequest, "error using state downstream auth params")
} }
// Grant the openid scope only if it was requested. // Automatically grant the openid and offline_access scopes, but only if they were requested.
grantOpenIDScopeIfRequested(authorizeRequester) oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOpenID)
oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOfflineAccess)
token, err := upstreamIDPConfig.ExchangeAuthcodeAndValidateTokens( token, err := upstreamIDPConfig.ExchangeAuthcodeAndValidateTokens(
r.Context(), r.Context(),
@ -189,14 +191,6 @@ func readState(r *http.Request, stateDecoder oidc.Decoder) (*oidc.UpstreamStateP
return &state, nil return &state, nil
} }
func grantOpenIDScopeIfRequested(authorizeRequester fosite.AuthorizeRequester) {
for _, scope := range authorizeRequester.GetRequestedScopes() {
if scope == "openid" {
authorizeRequester.GrantScope(scope)
}
}
}
func getUsernameFromUpstreamIDToken( func getUsernameFromUpstreamIDToken(
upstreamIDPConfig provider.UpstreamOIDCIdentityProviderI, upstreamIDPConfig provider.UpstreamOIDCIdentityProviderI,
idTokenClaims map[string]interface{}, idTokenClaims map[string]interface{},

View File

@ -66,7 +66,8 @@ const (
var ( var (
upstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"} upstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"}
happyDownstreamScopesRequested = []string{"openid", "profile", "email"} happyDownstreamScopesRequested = []string{"openid"}
happyDownstreamScopesGranted = []string{"openid"}
happyDownstreamRequestParamsQuery = url.Values{ happyDownstreamRequestParamsQuery = url.Values{
"response_type": []string{"code"}, "response_type": []string{"code"},
@ -127,7 +128,7 @@ func TestCallbackEndpoint(t *testing.T) {
wantStatus int wantStatus int
wantBody string wantBody string
wantRedirectLocationRegexp string wantRedirectLocationRegexp string
wantGrantedOpenidScope bool wantDownstreamGrantedScopes []string
wantDownstreamIDTokenSubject string wantDownstreamIDTokenSubject string
wantDownstreamIDTokenGroups []string wantDownstreamIDTokenGroups []string
wantDownstreamRequestedScopes []string wantDownstreamRequestedScopes []string
@ -145,11 +146,11 @@ func TestCallbackEndpoint(t *testing.T) {
csrfCookie: happyCSRFCookie, csrfCookie: happyCSRFCookie,
wantStatus: http.StatusFound, wantStatus: http.StatusFound,
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
wantGrantedOpenidScope: true,
wantBody: "", wantBody: "",
wantDownstreamIDTokenSubject: upstreamUsername, wantDownstreamIDTokenSubject: upstreamUsername,
wantDownstreamIDTokenGroups: upstreamGroupMembership, wantDownstreamIDTokenGroups: upstreamGroupMembership,
wantDownstreamRequestedScopes: happyDownstreamScopesRequested, wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
wantDownstreamNonce: downstreamNonce, wantDownstreamNonce: downstreamNonce,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
@ -163,11 +164,11 @@ func TestCallbackEndpoint(t *testing.T) {
csrfCookie: happyCSRFCookie, csrfCookie: happyCSRFCookie,
wantStatus: http.StatusFound, wantStatus: http.StatusFound,
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
wantGrantedOpenidScope: true,
wantBody: "", wantBody: "",
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject, wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
wantDownstreamIDTokenGroups: nil, wantDownstreamIDTokenGroups: nil,
wantDownstreamRequestedScopes: happyDownstreamScopesRequested, wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
wantDownstreamNonce: downstreamNonce, wantDownstreamNonce: downstreamNonce,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
@ -181,11 +182,11 @@ func TestCallbackEndpoint(t *testing.T) {
csrfCookie: happyCSRFCookie, csrfCookie: happyCSRFCookie,
wantStatus: http.StatusFound, wantStatus: http.StatusFound,
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
wantGrantedOpenidScope: true,
wantBody: "", wantBody: "",
wantDownstreamIDTokenSubject: upstreamSubject, wantDownstreamIDTokenSubject: upstreamSubject,
wantDownstreamIDTokenGroups: upstreamGroupMembership, wantDownstreamIDTokenGroups: upstreamGroupMembership,
wantDownstreamRequestedScopes: happyDownstreamScopesRequested, wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
wantDownstreamNonce: downstreamNonce, wantDownstreamNonce: downstreamNonce,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge, wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
@ -316,6 +317,28 @@ func TestCallbackEndpoint(t *testing.T) {
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs, wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
}, },
{
name: "state's downstream auth params also included offline_access scope",
idp: happyUpstream().Build(),
method: http.MethodGet,
path: newRequestPath().
WithState(
happyUpstreamStateParam().
WithAuthorizeRequestParams(shallowCopyAndModifyQuery(happyDownstreamRequestParamsQuery, map[string]string{"scope": "openid offline_access"}).Encode()).
Build(t, happyStateCodec),
).String(),
csrfCookie: happyCSRFCookie,
wantStatus: http.StatusFound,
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid%20offline_access&state=` + happyDownstreamState,
wantDownstreamIDTokenSubject: upstreamUsername,
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
wantDownstreamGrantedScopes: []string{"openid", "offline_access"},
wantDownstreamIDTokenGroups: upstreamGroupMembership,
wantDownstreamNonce: downstreamNonce,
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs,
},
{ {
name: "the UpstreamOIDCProvider CRD has been deleted", name: "the UpstreamOIDCProvider CRD has been deleted",
idp: otherUpstreamOIDCIdentityProvider, idp: otherUpstreamOIDCIdentityProvider,
@ -481,7 +504,7 @@ func TestCallbackEndpoint(t *testing.T) {
// Several Secrets should have been created // Several Secrets should have been created
expectedNumberOfCreatedSecrets := 2 expectedNumberOfCreatedSecrets := 2
if test.wantGrantedOpenidScope { if includesOpenIDScope(test.wantDownstreamGrantedScopes) {
expectedNumberOfCreatedSecrets++ expectedNumberOfCreatedSecrets++
} }
require.Len(t, client.Actions(), expectedNumberOfCreatedSecrets) require.Len(t, client.Actions(), expectedNumberOfCreatedSecrets)
@ -493,7 +516,7 @@ func TestCallbackEndpoint(t *testing.T) {
t, t,
oauthStore, oauthStore,
authcodeDataAndSignature[1], // Authcode store key is authcode signature authcodeDataAndSignature[1], // Authcode store key is authcode signature
test.wantGrantedOpenidScope, test.wantDownstreamGrantedScopes,
test.wantDownstreamIDTokenSubject, test.wantDownstreamIDTokenSubject,
test.wantDownstreamIDTokenGroups, test.wantDownstreamIDTokenGroups,
test.wantDownstreamRequestedScopes, test.wantDownstreamRequestedScopes,
@ -513,7 +536,7 @@ func TestCallbackEndpoint(t *testing.T) {
) )
// One IDSession should have been stored, if the downstream actually requested the "openid" scope // One IDSession should have been stored, if the downstream actually requested the "openid" scope
if test.wantGrantedOpenidScope { if includesOpenIDScope(test.wantDownstreamGrantedScopes) {
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1) testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secrets, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
validateIDSessionStorage( validateIDSessionStorage(
@ -530,6 +553,15 @@ func TestCallbackEndpoint(t *testing.T) {
} }
} }
func includesOpenIDScope(scopes []string) bool {
for _, scope := range scopes {
if scope == "openid" {
return true
}
}
return false
}
type requestPath struct { type requestPath struct {
code, state *string code, state *string
} }
@ -704,7 +736,7 @@ func validateAuthcodeStorage(
t *testing.T, t *testing.T,
oauthStore *oidc.KubeStorage, oauthStore *oidc.KubeStorage,
storeKey string, storeKey string,
wantGrantedOpenidScope bool, wantDownstreamGrantedScopes []string,
wantDownstreamIDTokenSubject string, wantDownstreamIDTokenSubject string,
wantDownstreamIDTokenGroups []string, wantDownstreamIDTokenGroups []string,
wantDownstreamRequestedScopes []string, wantDownstreamRequestedScopes []string,
@ -719,11 +751,7 @@ func validateAuthcodeStorage(
storedRequestFromAuthcode, storedSessionFromAuthcode := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromAuthcode) storedRequestFromAuthcode, storedSessionFromAuthcode := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromAuthcode)
// Check which scopes were granted. // Check which scopes were granted.
if wantGrantedOpenidScope { require.ElementsMatch(t, wantDownstreamGrantedScopes, storedRequestFromAuthcode.GetGrantedScopes())
require.Contains(t, storedRequestFromAuthcode.GetGrantedScopes(), "openid")
} else {
require.NotContains(t, storedRequestFromAuthcode.GetGrantedScopes(), "openid")
}
// Check all the other fields of the stored request. // Check all the other fields of the stored request.
require.NotEmpty(t, storedRequestFromAuthcode.ID) require.NotEmpty(t, storedRequestFromAuthcode.ID)

View File

@ -41,73 +41,147 @@ func NewKubeStorage(secrets corev1client.SecretInterface) *KubeStorage {
} }
} }
func (k KubeStorage) RevokeRefreshToken(ctx context.Context, requestID string) error { //
return k.refreshTokenStorage.RevokeRefreshToken(ctx, requestID) // Authorization Code sessions:
//
// These are keyed by the signature of the authcode.
//
// Fosite will create these in the authorize endpoint.
//
// Fosite will never delete them. Instead, it wants to mark them as invalidated once the authcode is used to redeem tokens.
// That way, it can later detect the case where an authcode that was already redeemed gets used again.
//
func (k KubeStorage) CreateAuthorizeCodeSession(ctx context.Context, signatureOfAuthcode string, r fosite.Requester) (err error) {
return k.authorizationCodeStorage.CreateAuthorizeCodeSession(ctx, signatureOfAuthcode, r)
}
func (k KubeStorage) GetAuthorizeCodeSession(ctx context.Context, signatureOfAuthcode string, s fosite.Session) (request fosite.Requester, err error) {
return k.authorizationCodeStorage.GetAuthorizeCodeSession(ctx, signatureOfAuthcode, s)
}
func (k KubeStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signatureOfAuthcode string) (err error) {
return k.authorizationCodeStorage.InvalidateAuthorizeCodeSession(ctx, signatureOfAuthcode)
}
//
// PKCE sessions:
//
// These are keyed by the signature of the authcode.
//
// Fosite will create these in the authorize endpoint at the same time that it is creating an authcode.
//
// Fosite will delete these in the token endpoint during authcode redemption since they are no longer needed after that.
// If the user chooses to never redeem their authcode, then fosite will never delete these.
//
func (k KubeStorage) CreatePKCERequestSession(ctx context.Context, signatureOfAuthcode string, requester fosite.Requester) error {
return k.pkceStorage.CreatePKCERequestSession(ctx, signatureOfAuthcode, requester)
}
func (k KubeStorage) GetPKCERequestSession(ctx context.Context, signatureOfAuthcode string, session fosite.Session) (fosite.Requester, error) {
return k.pkceStorage.GetPKCERequestSession(ctx, signatureOfAuthcode, session)
}
func (k KubeStorage) DeletePKCERequestSession(ctx context.Context, signatureOfAuthcode string) error {
return k.pkceStorage.DeletePKCERequestSession(ctx, signatureOfAuthcode)
}
//
// OpenID Connect sessions:
//
// These are keyed by the full value of the authcode (not just the signature).
//
// Fosite will create these in the authorize endpoint when it creates an authcode, but only if the user
// requested the openid scope.
//
// Fosite will never delete these, which is likely a bug in fosite. Although there is a delete method below, fosite
// never calls it. Used during authcode redemption, they will never be accessed again after a successful authcode
// redemption. Although that implies that they should probably follow a lifecycle similar the the PKCE storage, they
// are, in fact, not deleted.
//
func (k KubeStorage) CreateOpenIDConnectSession(ctx context.Context, fullAuthcode string, requester fosite.Requester) error {
return k.oidcStorage.CreateOpenIDConnectSession(ctx, fullAuthcode, requester)
}
func (k KubeStorage) GetOpenIDConnectSession(ctx context.Context, fullAuthcode string, requester fosite.Requester) (fosite.Requester, error) {
return k.oidcStorage.GetOpenIDConnectSession(ctx, fullAuthcode, requester)
}
func (k KubeStorage) DeleteOpenIDConnectSession(ctx context.Context, fullAuthcode string) error {
return k.oidcStorage.DeleteOpenIDConnectSession(ctx, fullAuthcode)
}
//
// Access token sessions:
//
// These are keyed by the signature of the access token.
//
// Fosite will create these in the token endpoint whenever it wants to hand out an access token, including the original
// authcode redemption and also during refresh.
//
// Fosite will not use the delete method. Instead, it will use the revoke method to delete them.
// During a refresh in the token endpoint, the old access token is revoked just before the new access token is created.
// Also, if the token endpoint receives an authcode that was already used successfully, then it revokes the access token
// that was previously handed out for that authcode. If a user stops coming back to refresh their tokens, then that
// access token will never be deleted.
//
func (k KubeStorage) CreateAccessTokenSession(ctx context.Context, signatureOfAccessToken string, requester fosite.Requester) (err error) {
return k.accessTokenStorage.CreateAccessTokenSession(ctx, signatureOfAccessToken, requester)
}
func (k KubeStorage) GetAccessTokenSession(ctx context.Context, signatureOfAccessToken string, session fosite.Session) (request fosite.Requester, err error) {
return k.accessTokenStorage.GetAccessTokenSession(ctx, signatureOfAccessToken, session)
}
func (k KubeStorage) DeleteAccessTokenSession(ctx context.Context, signatureOfAccessToken string) (err error) {
return k.accessTokenStorage.DeleteAccessTokenSession(ctx, signatureOfAccessToken)
} }
func (k KubeStorage) RevokeAccessToken(ctx context.Context, requestID string) error { func (k KubeStorage) RevokeAccessToken(ctx context.Context, requestID string) error {
return k.accessTokenStorage.RevokeAccessToken(ctx, requestID) return k.accessTokenStorage.RevokeAccessToken(ctx, requestID)
} }
func (k KubeStorage) CreateRefreshTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) { //
return k.refreshTokenStorage.CreateRefreshTokenSession(ctx, signature, request) // Refresh token sessions:
//
// These are keyed by the signature of the refresh token.
//
// Fosite will create these in the token endpoint whenever it wants to hand out an refresh token, including the original
// authcode redemption and also during refresh. Refresh tokens are only handed out when the user requested the
// offline_access scope on the original authorization request.
//
// Fosite will not use the delete method. Instead, it will use the revoke method to delete them.
// During a refresh in the token endpoint, the old refresh token is revoked just before the new refresh token is created.
// Also, if the token endpoint receives an authcode that was already used successfully, then it revokes the refresh token
// that was previously handed out for that authcode. If a user stops coming back to refresh their tokens, then that
// refresh token will never be deleted.
//
func (k KubeStorage) CreateRefreshTokenSession(ctx context.Context, signatureOfRefreshToken string, request fosite.Requester) (err error) {
return k.refreshTokenStorage.CreateRefreshTokenSession(ctx, signatureOfRefreshToken, request)
} }
func (k KubeStorage) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { func (k KubeStorage) GetRefreshTokenSession(ctx context.Context, signatureOfRefreshToken string, session fosite.Session) (request fosite.Requester, err error) {
return k.refreshTokenStorage.GetRefreshTokenSession(ctx, signature, session) return k.refreshTokenStorage.GetRefreshTokenSession(ctx, signatureOfRefreshToken, session)
} }
func (k KubeStorage) DeleteRefreshTokenSession(ctx context.Context, signature string) (err error) { func (k KubeStorage) DeleteRefreshTokenSession(ctx context.Context, signatureOfRefreshToken string) (err error) {
return k.refreshTokenStorage.DeleteRefreshTokenSession(ctx, signature) return k.refreshTokenStorage.DeleteRefreshTokenSession(ctx, signatureOfRefreshToken)
} }
func (k KubeStorage) CreateAccessTokenSession(ctx context.Context, signature string, requester fosite.Requester) (err error) { func (k KubeStorage) RevokeRefreshToken(ctx context.Context, requestID string) error {
return k.accessTokenStorage.CreateAccessTokenSession(ctx, signature, requester) return k.refreshTokenStorage.RevokeRefreshToken(ctx, requestID)
} }
func (k KubeStorage) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { //
return k.accessTokenStorage.GetAccessTokenSession(ctx, signature, session) // OAuth client definitions:
} //
// For the time being, we only allow a single pre-defined client, so we do not need to interact with any underlying
func (k KubeStorage) DeleteAccessTokenSession(ctx context.Context, signature string) (err error) { // storage mechanism to fetch them.
return k.accessTokenStorage.DeleteAccessTokenSession(ctx, signature) //
}
func (k KubeStorage) CreateOpenIDConnectSession(ctx context.Context, authcode string, requester fosite.Requester) error {
return k.oidcStorage.CreateOpenIDConnectSession(ctx, authcode, requester)
}
func (k KubeStorage) GetOpenIDConnectSession(ctx context.Context, authcode string, requester fosite.Requester) (fosite.Requester, error) {
return k.oidcStorage.GetOpenIDConnectSession(ctx, authcode, requester)
}
func (k KubeStorage) DeleteOpenIDConnectSession(ctx context.Context, authcode string) error {
return k.oidcStorage.DeleteOpenIDConnectSession(ctx, authcode)
}
func (k KubeStorage) GetPKCERequestSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error) {
return k.pkceStorage.GetPKCERequestSession(ctx, signature, session)
}
func (k KubeStorage) CreatePKCERequestSession(ctx context.Context, signature string, requester fosite.Requester) error {
return k.pkceStorage.CreatePKCERequestSession(ctx, signature, requester)
}
func (k KubeStorage) DeletePKCERequestSession(ctx context.Context, signature string) error {
return k.pkceStorage.DeletePKCERequestSession(ctx, signature)
}
func (k KubeStorage) CreateAuthorizeCodeSession(ctx context.Context, signature string, r fosite.Requester) (err error) {
return k.authorizationCodeStorage.CreateAuthorizeCodeSession(ctx, signature, r)
}
func (k KubeStorage) GetAuthorizeCodeSession(ctx context.Context, signature string, s fosite.Session) (request fosite.Requester, err error) {
return k.authorizationCodeStorage.GetAuthorizeCodeSession(ctx, signature, s)
}
func (k KubeStorage) InvalidateAuthorizeCodeSession(ctx context.Context, signature string) (err error) {
return k.authorizationCodeStorage.InvalidateAuthorizeCodeSession(ctx, signature)
}
func (KubeStorage) GetClient(_ context.Context, id string) (fosite.Client, error) { func (KubeStorage) GetClient(_ context.Context, id string) (fosite.Client, error) {
client := PinnipedCLIOIDCClient() client := PinnipedCLIOIDCClient()
@ -117,6 +191,10 @@ func (KubeStorage) GetClient(_ context.Context, id string) (fosite.Client, error
return nil, fosite.ErrNotFound return nil, fosite.ErrNotFound
} }
//
// Unused interface methods.
//
func (KubeStorage) ClientAssertionJWTValid(_ context.Context, _ string) error { func (KubeStorage) ClientAssertionJWTValid(_ context.Context, _ string) error {
return errKubeStorageNotImplemented return errKubeStorageNotImplemented
} }

View File

@ -27,8 +27,8 @@ func TestNullStorage_GetClient(t *testing.T) {
Public: true, Public: true,
RedirectURIs: []string{"http://127.0.0.1/callback"}, RedirectURIs: []string{"http://127.0.0.1/callback"},
ResponseTypes: []string{"code"}, ResponseTypes: []string{"code"},
GrantTypes: []string{"authorization_code"}, GrantTypes: []string{"authorization_code", "refresh_token"},
Scopes: []string{"openid", "profile", "email"}, Scopes: []string{"openid", "offline_access", "profile", "email"},
}, },
TokenEndpointAuthMethod: "none", TokenEndpointAuthMethod: "none",
}, },

View File

@ -7,6 +7,7 @@ package oidc
import ( import (
"time" "time"
coreosoidc "github.com/coreos/go-oidc"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/compose" "github.com/ory/fosite/compose"
@ -83,8 +84,8 @@ func PinnipedCLIOIDCClient() *fosite.DefaultOpenIDConnectClient {
Public: true, Public: true,
RedirectURIs: []string{"http://127.0.0.1/callback"}, RedirectURIs: []string{"http://127.0.0.1/callback"},
ResponseTypes: []string{"code"}, ResponseTypes: []string{"code"},
GrantTypes: []string{"authorization_code"}, GrantTypes: []string{"authorization_code", "refresh_token"},
Scopes: []string{"openid", "profile", "email"}, Scopes: []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, "profile", "email"},
}, },
TokenEndpointAuthMethod: "none", TokenEndpointAuthMethod: "none",
} }
@ -111,7 +112,8 @@ func FositeOauth2Helper(
EnforcePKCE: true, // follow current set of best practices and always require PKCE EnforcePKCE: true, // follow current set of best practices and always require PKCE
AllowedPromptValues: []string{"none"}, // TODO unclear what we should set here AllowedPromptValues: []string{"none"}, // TODO unclear what we should set here
RefreshTokenScopes: nil, // TODO decide what makes sense when we add refresh token support RefreshTokenScopes: []string{coreosoidc.ScopeOfflineAccess}, // as per https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
MinParameterEntropy: 32, // 256 bits seems about right MinParameterEntropy: 32, // 256 bits seems about right
} }
@ -125,9 +127,9 @@ func FositeOauth2Helper(
}, },
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets. nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
compose.OAuth2AuthorizeExplicitFactory, compose.OAuth2AuthorizeExplicitFactory,
// compose.OAuth2RefreshTokenGrantFactory, compose.OAuth2RefreshTokenGrantFactory,
compose.OpenIDConnectExplicitFactory, compose.OpenIDConnectExplicitFactory,
// compose.OpenIDConnectRefreshFactory, compose.OpenIDConnectRefreshFactory,
compose.OAuth2PKCEFactory, compose.OAuth2PKCEFactory,
) )
} }
@ -156,3 +158,11 @@ func FositeErrorForLog(err error) []interface{} {
type IDPListGetter interface { type IDPListGetter interface {
GetIDPList() []provider.UpstreamOIDCIdentityProviderI GetIDPList() []provider.UpstreamOIDCIdentityProviderI
} }
func GrantScopeIfRequested(authorizeRequester fosite.AuthorizeRequester, scopeName string) {
for _, scope := range authorizeRequester.GetRequestedScopes() {
if scope == scopeName {
authorizeRequester.GrantScope(scope)
}
}
}

File diff suppressed because it is too large Load Diff