require groups scope to get groups back from supervisor
Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
parent
268e1108d1
commit
4d0c2e16f4
@ -146,7 +146,7 @@ func handleAuthRequestForLDAPUpstreamCLIFlow(
|
|||||||
username = authenticateResponse.User.GetName()
|
username = authenticateResponse.User.GetName()
|
||||||
groups := authenticateResponse.User.GetGroups()
|
groups := authenticateResponse.User.GetGroups()
|
||||||
customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream, idpType, authenticateResponse)
|
customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream, idpType, authenticateResponse)
|
||||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, authorizeRequester.GetGrantedScopes(), customSessionData)
|
||||||
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, true)
|
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, true)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -243,7 +243,7 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, authorizeRequester.GetGrantedScopes(), customSessionData)
|
||||||
|
|
||||||
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, true)
|
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, true)
|
||||||
|
|
||||||
@ -334,7 +334,7 @@ func newAuthorizeRequest(r *http.Request, w http.ResponseWriter, oauthHelper fos
|
|||||||
// 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.
|
||||||
// There don't seem to be any validations inside `NewAuthorizeResponse` related to the offline_access scope
|
// 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.
|
// at this time, however we will temporarily grant the scope just in case that changes in a future release of fosite.
|
||||||
downstreamsession.GrantScopesIfRequested(authorizeRequester)
|
downstreamsession.GrantScopesIfRequested(authorizeRequester, []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, oidc.RequestAudienceScope, oidc.DownstreamGroupsScope})
|
||||||
|
|
||||||
return authorizeRequester, true
|
return authorizeRequester, true
|
||||||
}
|
}
|
||||||
|
@ -375,8 +375,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
return urlToReturn
|
return urlToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
happyDownstreamScopesRequested := []string{"openid", "profile", "email"}
|
happyDownstreamScopesRequested := []string{"openid", "profile", "email", "groups"}
|
||||||
happyDownstreamScopesGranted := []string{"openid"}
|
happyDownstreamScopesGranted := []string{"openid", "groups"}
|
||||||
|
|
||||||
happyGetRequestQueryMap := map[string]string{
|
happyGetRequestQueryMap := map[string]string{
|
||||||
"response_type": "code",
|
"response_type": "code",
|
||||||
@ -495,7 +495,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
||||||
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid&state=` + happyState
|
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+groups&state=` + happyState
|
||||||
|
|
||||||
incomingCookieCSRFValue := "csrf-value-from-cookie"
|
incomingCookieCSRFValue := "csrf-value-from-cookie"
|
||||||
encodedIncomingCookieCSRFValue, err := happyCookieEncoder.Encode("csrf", incomingCookieCSRFValue)
|
encodedIncomingCookieCSRFValue, err := happyCookieEncoder.Encode("csrf", incomingCookieCSRFValue)
|
||||||
@ -957,7 +957,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid&state=` + happyState,
|
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid\+groups&state=` + happyState,
|
||||||
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
@ -980,7 +980,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
customPasswordHeader: pointer.StringPtr(happyLDAPPassword),
|
||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid&state=` + happyState,
|
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid\+groups&state=` + happyState,
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
@ -52,7 +53,7 @@ func NewHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Automatically grant the openid, offline_access, and pinniped:request-audience scopes, but only if they were requested.
|
// Automatically grant the openid, offline_access, and pinniped:request-audience scopes, but only if they were requested.
|
||||||
downstreamsession.GrantScopesIfRequested(authorizeRequester)
|
downstreamsession.GrantScopesIfRequested(authorizeRequester, []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, oidc.RequestAudienceScope, oidc.DownstreamGroupsScope})
|
||||||
|
|
||||||
token, err := upstreamIDPConfig.ExchangeAuthcodeAndValidateTokens(
|
token, err := upstreamIDPConfig.ExchangeAuthcodeAndValidateTokens(
|
||||||
r.Context(),
|
r.Context(),
|
||||||
@ -76,7 +77,7 @@ func NewHandler(
|
|||||||
return httperr.Wrap(http.StatusUnprocessableEntity, err.Error(), err)
|
return httperr.Wrap(http.StatusUnprocessableEntity, err.Error(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, authorizeRequester.GetGrantedScopes(), customSessionData)
|
||||||
|
|
||||||
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,8 +62,8 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
oidcUpstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"}
|
oidcUpstreamGroupMembership = []string{"test-pinniped-group-0", "test-pinniped-group-1"}
|
||||||
happyDownstreamScopesRequested = []string{"openid"}
|
happyDownstreamScopesRequested = []string{"openid", "groups"}
|
||||||
happyDownstreamScopesGranted = []string{"openid"}
|
happyDownstreamScopesGranted = []string{"openid", "groups"}
|
||||||
|
|
||||||
happyDownstreamRequestParamsQuery = url.Values{
|
happyDownstreamRequestParamsQuery = url.Values{
|
||||||
"response_type": []string{"code"},
|
"response_type": []string{"code"},
|
||||||
@ -133,7 +133,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
||||||
happyDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid&state=` + happyDownstreamState
|
happyDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+groups&state=` + happyDownstreamState
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -236,6 +236,38 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
args: happyExchangeAndValidateTokensArgs,
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "form_post happy path with no groups scope requested",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
|
method: http.MethodGet,
|
||||||
|
path: newRequestPath().WithState(
|
||||||
|
happyUpstreamStateParam().WithAuthorizeRequestParams(
|
||||||
|
shallowCopyAndModifyQuery(
|
||||||
|
happyDownstreamRequestParamsQuery,
|
||||||
|
map[string]string{
|
||||||
|
"response_mode": "form_post",
|
||||||
|
"scope": "openid",
|
||||||
|
},
|
||||||
|
).Encode(),
|
||||||
|
).Build(t, happyStateCodec),
|
||||||
|
).String(),
|
||||||
|
csrfCookie: happyCSRFCookie,
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantContentType: "text/html;charset=UTF-8",
|
||||||
|
wantBodyFormResponseRegexp: `<code id="manual-auth-code">(.+)</code>`,
|
||||||
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
|
wantDownstreamRequestedScopes: []string{"openid"},
|
||||||
|
wantDownstreamGrantedScopes: []string{"openid"},
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||||
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "GET with authcode exchange that returns an access token but no refresh token but has a short token lifetime which is stored as a warning in the session",
|
name: "GET with authcode exchange that returns an access token but no refresh token but has a short token lifetime which is stored as a warning in the session",
|
||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().WithEmptyRefreshToken().WithAccessToken(oidcUpstreamAccessToken, metav1.NewTime(time.Now().Add(1*time.Hour))).WithUserInfoURL().Build()),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().WithEmptyRefreshToken().WithAccessToken(oidcUpstreamAccessToken, metav1.NewTime(time.Now().Add(1*time.Hour))).WithUserInfoURL().Build()),
|
||||||
@ -683,6 +715,33 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
name: "state's downstream auth params does not contain openid scope",
|
name: "state's downstream auth params does not contain openid scope",
|
||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
method: http.MethodGet,
|
method: http.MethodGet,
|
||||||
|
path: newRequestPath().
|
||||||
|
WithState(
|
||||||
|
happyUpstreamStateParam().
|
||||||
|
WithAuthorizeRequestParams(shallowCopyAndModifyQuery(happyDownstreamRequestParamsQuery, map[string]string{"scope": "profile email groups"}).Encode()).
|
||||||
|
Build(t, happyStateCodec),
|
||||||
|
).String(),
|
||||||
|
csrfCookie: happyCSRFCookie,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=groups&state=` + happyDownstreamState,
|
||||||
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
|
wantDownstreamRequestedScopes: []string{"profile", "email", "groups"},
|
||||||
|
wantDownstreamGrantedScopes: []string{"groups"},
|
||||||
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||||
|
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||||
|
performedByUpstreamName: happyUpstreamIDPName,
|
||||||
|
args: happyExchangeAndValidateTokensArgs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "state's downstream auth params does not contain openid or groups scope",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().Build()),
|
||||||
|
method: http.MethodGet,
|
||||||
path: newRequestPath().
|
path: newRequestPath().
|
||||||
WithState(
|
WithState(
|
||||||
happyUpstreamStateParam().
|
happyUpstreamStateParam().
|
||||||
@ -695,7 +754,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamRequestedScopes: []string{"profile", "email"},
|
wantDownstreamRequestedScopes: []string{"profile", "email"},
|
||||||
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
wantDownstreamGrantedScopes: []string{},
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
@ -712,16 +771,16 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
path: newRequestPath().
|
path: newRequestPath().
|
||||||
WithState(
|
WithState(
|
||||||
happyUpstreamStateParam().
|
happyUpstreamStateParam().
|
||||||
WithAuthorizeRequestParams(shallowCopyAndModifyQuery(happyDownstreamRequestParamsQuery, map[string]string{"scope": "openid offline_access"}).Encode()).
|
WithAuthorizeRequestParams(shallowCopyAndModifyQuery(happyDownstreamRequestParamsQuery, map[string]string{"scope": "openid offline_access groups"}).Encode()).
|
||||||
Build(t, happyStateCodec),
|
Build(t, happyStateCodec),
|
||||||
).String(),
|
).String(),
|
||||||
csrfCookie: happyCSRFCookie,
|
csrfCookie: happyCSRFCookie,
|
||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+groups&state=` + happyDownstreamState,
|
||||||
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
wantDownstreamIDTokenUsername: oidcUpstreamUsername,
|
||||||
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped,
|
||||||
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
|
wantDownstreamRequestedScopes: []string{"openid", "offline_access", "groups"},
|
||||||
wantDownstreamGrantedScopes: []string{"openid", "offline_access"},
|
wantDownstreamGrantedScopes: []string{"openid", "offline_access", "groups"},
|
||||||
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
// Package clientregistry defines Pinniped's OAuth2/OIDC clients.
|
// Package clientregistry defines Pinniped's OAuth2/OIDC clients.
|
||||||
@ -85,6 +85,7 @@ func PinnipedCLI() *Client {
|
|||||||
"profile",
|
"profile",
|
||||||
"email",
|
"email",
|
||||||
"pinniped:request-audience",
|
"pinniped:request-audience",
|
||||||
|
"groups",
|
||||||
},
|
},
|
||||||
Audience: nil,
|
Audience: nil,
|
||||||
Public: true,
|
Public: true,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package clientregistry
|
package clientregistry
|
||||||
@ -50,7 +50,7 @@ func TestPinnipedCLI(t *testing.T) {
|
|||||||
require.Equal(t, []string{"http://127.0.0.1/callback"}, c.GetRedirectURIs())
|
require.Equal(t, []string{"http://127.0.0.1/callback"}, c.GetRedirectURIs())
|
||||||
require.Equal(t, fosite.Arguments{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"}, c.GetGrantTypes())
|
require.Equal(t, fosite.Arguments{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"}, c.GetGrantTypes())
|
||||||
require.Equal(t, fosite.Arguments{"code"}, c.GetResponseTypes())
|
require.Equal(t, fosite.Arguments{"code"}, c.GetResponseTypes())
|
||||||
require.Equal(t, fosite.Arguments{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email", "pinniped:request-audience"}, c.GetScopes())
|
require.Equal(t, fosite.Arguments{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email", "pinniped:request-audience", "groups"}, c.GetScopes())
|
||||||
require.True(t, c.IsPublic())
|
require.True(t, c.IsPublic())
|
||||||
require.Nil(t, c.GetAudience())
|
require.Nil(t, c.GetAudience())
|
||||||
require.Nil(t, c.GetRequestURIs())
|
require.Nil(t, c.GetRequestURIs())
|
||||||
@ -82,7 +82,8 @@ func TestPinnipedCLI(t *testing.T) {
|
|||||||
"offline_access",
|
"offline_access",
|
||||||
"profile",
|
"profile",
|
||||||
"email",
|
"email",
|
||||||
"pinniped:request-audience"
|
"pinniped:request-audience",
|
||||||
|
"groups"
|
||||||
],
|
],
|
||||||
"audience": null,
|
"audience": null,
|
||||||
"public": true,
|
"public": true,
|
||||||
|
@ -10,7 +10,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
"k8s.io/utils/strings/slices"
|
||||||
|
|
||||||
"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"
|
||||||
@ -40,7 +41,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// MakeDownstreamSession creates a downstream OIDC session.
|
// MakeDownstreamSession creates a downstream OIDC session.
|
||||||
func MakeDownstreamSession(subject string, username string, groups []string, custom *psession.CustomSessionData) *psession.PinnipedSession {
|
func MakeDownstreamSession(subject string, username string, groups []string, grantedScopes []string, custom *psession.CustomSessionData) *psession.PinnipedSession {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
openIDSession := &psession.PinnipedSession{
|
openIDSession := &psession.PinnipedSession{
|
||||||
Fosite: &openid.DefaultSession{
|
Fosite: &openid.DefaultSession{
|
||||||
@ -57,7 +58,9 @@ func MakeDownstreamSession(subject string, username string, groups []string, cus
|
|||||||
}
|
}
|
||||||
openIDSession.IDTokenClaims().Extra = map[string]interface{}{
|
openIDSession.IDTokenClaims().Extra = map[string]interface{}{
|
||||||
oidc.DownstreamUsernameClaim: username,
|
oidc.DownstreamUsernameClaim: username,
|
||||||
oidc.DownstreamGroupsClaim: groups,
|
}
|
||||||
|
if slices.Contains(grantedScopes, oidc.DownstreamGroupsScope) {
|
||||||
|
openIDSession.IDTokenClaims().Extra[oidc.DownstreamGroupsClaim] = groups
|
||||||
}
|
}
|
||||||
return openIDSession
|
return openIDSession
|
||||||
}
|
}
|
||||||
@ -147,10 +150,10 @@ func MakeDownstreamOIDCCustomSessionData(oidcUpstream provider.UpstreamOIDCIdent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GrantScopesIfRequested auto-grants the scopes for which we do not require end-user approval, if they were requested.
|
// GrantScopesIfRequested auto-grants the scopes for which we do not require end-user approval, if they were requested.
|
||||||
func GrantScopesIfRequested(authorizeRequester fosite.AuthorizeRequester) {
|
func GrantScopesIfRequested(authorizeRequester fosite.AuthorizeRequester, scopes []string) {
|
||||||
oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOpenID)
|
for _, scope := range scopes {
|
||||||
oidc.GrantScopeIfRequested(authorizeRequester, coreosoidc.ScopeOfflineAccess)
|
oidc.GrantScopeIfRequested(authorizeRequester, scope)
|
||||||
oidc.GrantScopeIfRequested(authorizeRequester, "pinniped:request-audience")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDownstreamIdentityFromUpstreamIDToken returns the mapped subject, username, and group names, in that order.
|
// GetDownstreamIdentityFromUpstreamIDToken returns the mapped subject, username, and group names, in that order.
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
@ -44,8 +46,8 @@ func NewPostHandler(issuerURL string, upstreamIDPs oidc.UpstreamIdentityProvider
|
|||||||
return httperr.New(http.StatusBadRequest, "error using state downstream auth params")
|
return httperr.New(http.StatusBadRequest, "error using state downstream auth params")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically grant the openid, offline_access, and pinniped:request-audience scopes, but only if they were requested.
|
// Automatically grant the openid, offline_access, pinniped:request-audience and groups scopes, but only if they were requested.
|
||||||
downstreamsession.GrantScopesIfRequested(authorizeRequester)
|
downstreamsession.GrantScopesIfRequested(authorizeRequester, []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, oidc.RequestAudienceScope, oidc.DownstreamGroupsScope})
|
||||||
|
|
||||||
// Get the username and password form params from the POST body.
|
// Get the username and password form params from the POST body.
|
||||||
username := r.PostFormValue(usernameParamName)
|
username := r.PostFormValue(usernameParamName)
|
||||||
@ -80,7 +82,7 @@ func NewPostHandler(issuerURL string, upstreamIDPs oidc.UpstreamIdentityProvider
|
|||||||
username = authenticateResponse.User.GetName()
|
username = authenticateResponse.User.GetName()
|
||||||
groups := authenticateResponse.User.GetGroups()
|
groups := authenticateResponse.User.GetGroups()
|
||||||
customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream, idpType, authenticateResponse)
|
customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream, idpType, authenticateResponse)
|
||||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, authorizeRequester.GetGrantedScopes(), customSessionData)
|
||||||
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, false)
|
oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, false)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -82,8 +82,8 @@ func TestPostLoginEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
happyDownstreamScopesRequested := []string{"openid"}
|
happyDownstreamScopesRequested := []string{"openid", "groups"}
|
||||||
happyDownstreamScopesGranted := []string{"openid"}
|
happyDownstreamScopesGranted := []string{"openid", "groups"}
|
||||||
|
|
||||||
happyDownstreamRequestParamsQuery := url.Values{
|
happyDownstreamRequestParamsQuery := url.Values{
|
||||||
"response_type": []string{"code"},
|
"response_type": []string{"code"},
|
||||||
@ -211,7 +211,7 @@ func TestPostLoginEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
||||||
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid&state=` + happyDownstreamState
|
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+groups&state=` + happyDownstreamState
|
||||||
|
|
||||||
happyUsernamePasswordFormParams := url.Values{userParam: []string{happyLDAPUsername}, passParam: []string{happyLDAPPassword}}
|
happyUsernamePasswordFormParams := url.Values{userParam: []string{happyLDAPUsername}, passParam: []string{happyLDAPPassword}}
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ func TestPostLoginEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusSeeOther,
|
wantStatus: http.StatusSeeOther,
|
||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantBodyString: "",
|
wantBodyString: "",
|
||||||
wantRedirectLocationRegexp: "http://127.0.0.1:4242/callback" + `\?code=([^&]+)&scope=openid&state=` + happyDownstreamState,
|
wantRedirectLocationRegexp: "http://127.0.0.1:4242/callback" + `\?code=([^&]+)&scope=openid\+groups&state=` + happyDownstreamState,
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
@ -410,6 +410,31 @@ func TestPostLoginEndpoint(t *testing.T) {
|
|||||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "happy LDAP login when groups scope is not requested",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().
|
||||||
|
WithLDAP(&upstreamLDAPIdentityProvider). // should pick this one
|
||||||
|
WithActiveDirectory(&erroringUpstreamLDAPIdentityProvider),
|
||||||
|
decodedState: modifyHappyLDAPDecodedState(func(data *oidc.UpstreamStateParamData) {
|
||||||
|
query := copyOfHappyDownstreamRequestParamsQuery()
|
||||||
|
query["scope"] = []string{"openid"}
|
||||||
|
data.AuthParams = query.Encode()
|
||||||
|
}),
|
||||||
|
formParams: happyUsernamePasswordFormParams,
|
||||||
|
wantStatus: http.StatusSeeOther,
|
||||||
|
wantContentType: htmlContentType,
|
||||||
|
wantBodyString: "",
|
||||||
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid&state=` + happyDownstreamState,
|
||||||
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
|
wantDownstreamRequestedScopes: []string{"openid"},
|
||||||
|
wantDownstreamRedirectURI: downstreamRedirectURI,
|
||||||
|
wantDownstreamGrantedScopes: []string{"openid"},
|
||||||
|
wantDownstreamNonce: downstreamNonce,
|
||||||
|
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||||
|
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||||
|
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "bad username LDAP login",
|
name: "bad username LDAP login",
|
||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(&upstreamLDAPIdentityProvider),
|
||||||
|
@ -76,6 +76,14 @@ const (
|
|||||||
// information.
|
// information.
|
||||||
DownstreamGroupsClaim = "groups"
|
DownstreamGroupsClaim = "groups"
|
||||||
|
|
||||||
|
// DownstreamGroupsScope is a custom scope that determines whether the
|
||||||
|
// groups claim will be returned in ID tokens.
|
||||||
|
DownstreamGroupsScope = "groups"
|
||||||
|
|
||||||
|
// RequestAudienceScope is a custom scope that determines whether a RFC8693 token
|
||||||
|
// exchange is allowed to request a different audience.
|
||||||
|
RequestAudienceScope = "pinniped:request-audience"
|
||||||
|
|
||||||
// CSRFCookieLifespan is the length of time that the CSRF cookie is valid. After this time, the
|
// CSRFCookieLifespan is the length of time that the CSRF cookie is valid. After this time, the
|
||||||
// Supervisor's authorization endpoint should give the browser a new CSRF cookie. We set it to
|
// Supervisor's authorization endpoint should give the browser a new CSRF cookie. We set it to
|
||||||
// a week so that it is unlikely to expire during a login.
|
// a week so that it is unlikely to expire during a login.
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/utils/strings/slices"
|
||||||
|
|
||||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
@ -1063,10 +1065,16 @@ func validateAuthcodeStorage(
|
|||||||
// Check the user's identity, which are put into the downstream ID token's subject, username and groups claims.
|
// Check the user's identity, which are put into the downstream ID token's subject, username and groups claims.
|
||||||
require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject)
|
require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject)
|
||||||
require.Equal(t, wantDownstreamIDTokenUsername, actualClaims.Extra["username"])
|
require.Equal(t, wantDownstreamIDTokenUsername, actualClaims.Extra["username"])
|
||||||
|
if slices.Contains(wantDownstreamGrantedScopes, "groups") {
|
||||||
require.Len(t, actualClaims.Extra, 2)
|
require.Len(t, actualClaims.Extra, 2)
|
||||||
actualDownstreamIDTokenGroups := actualClaims.Extra["groups"]
|
actualDownstreamIDTokenGroups := actualClaims.Extra["groups"]
|
||||||
require.NotNil(t, actualDownstreamIDTokenGroups)
|
require.NotNil(t, actualDownstreamIDTokenGroups)
|
||||||
require.ElementsMatch(t, wantDownstreamIDTokenGroups, actualDownstreamIDTokenGroups)
|
require.ElementsMatch(t, wantDownstreamIDTokenGroups, actualDownstreamIDTokenGroups)
|
||||||
|
} else {
|
||||||
|
require.Len(t, actualClaims.Extra, 1)
|
||||||
|
actualDownstreamIDTokenGroups := actualClaims.Extra["groups"]
|
||||||
|
require.Nil(t, actualDownstreamIDTokenGroups)
|
||||||
|
}
|
||||||
|
|
||||||
// Check the rest of the downstream ID token's claims. Fosite wants us to set these (in UTC time).
|
// Check the rest of the downstream ID token's claims. Fosite wants us to set these (in UTC time).
|
||||||
testutil.RequireTimeInDelta(t, time.Now().UTC(), actualClaims.RequestedAt, timeComparisonFudgeFactor)
|
testutil.RequireTimeInDelta(t, time.Now().UTC(), actualClaims.RequestedAt, timeComparisonFudgeFactor)
|
||||||
|
@ -170,6 +170,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--oidc-skip-browser",
|
"--oidc-skip-browser",
|
||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
||||||
@ -256,6 +257,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--oidc-skip-listen",
|
"--oidc-skip-listen",
|
||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
||||||
@ -381,6 +383,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--oidc-skip-listen",
|
"--oidc-skip-listen",
|
||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
||||||
@ -514,6 +517,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--upstream-identity-provider-flow", "cli_password", // create a kubeconfig configured to use the cli_password flow
|
"--upstream-identity-provider-flow", "cli_password", // create a kubeconfig configured to use the cli_password flow
|
||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger a browser-less CLI prompt login via the plugin.
|
// Run "kubectl get namespaces" which should trigger a browser-less CLI prompt login via the plugin.
|
||||||
@ -594,6 +598,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--upstream-identity-provider-flow", "cli_password",
|
"--upstream-identity-provider-flow", "cli_password",
|
||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get --raw /healthz" which should trigger a browser-less CLI prompt login via the plugin.
|
// Run "kubectl get --raw /healthz" which should trigger a browser-less CLI prompt login via the plugin.
|
||||||
@ -655,6 +660,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--concierge-authenticator-type", "jwt",
|
"--concierge-authenticator-type", "jwt",
|
||||||
"--concierge-authenticator-name", authenticator.Name,
|
"--concierge-authenticator-name", authenticator.Name,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger an LDAP-style login CLI prompt via the plugin.
|
// Run "kubectl get namespaces" which should trigger an LDAP-style login CLI prompt via the plugin.
|
||||||
@ -715,6 +721,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--concierge-authenticator-type", "jwt",
|
"--concierge-authenticator-type", "jwt",
|
||||||
"--concierge-authenticator-name", authenticator.Name,
|
"--concierge-authenticator-name", authenticator.Name,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set up the username and password env vars to avoid the interactive prompts.
|
// Set up the username and password env vars to avoid the interactive prompts.
|
||||||
@ -787,6 +794,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--concierge-authenticator-type", "jwt",
|
"--concierge-authenticator-type", "jwt",
|
||||||
"--concierge-authenticator-name", authenticator.Name,
|
"--concierge-authenticator-name", authenticator.Name,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger an LDAP-style login CLI prompt via the plugin.
|
// Run "kubectl get namespaces" which should trigger an LDAP-style login CLI prompt via the plugin.
|
||||||
@ -847,6 +855,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--concierge-authenticator-type", "jwt",
|
"--concierge-authenticator-type", "jwt",
|
||||||
"--concierge-authenticator-name", authenticator.Name,
|
"--concierge-authenticator-name", authenticator.Name,
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set up the username and password env vars to avoid the interactive prompts.
|
// Set up the username and password env vars to avoid the interactive prompts.
|
||||||
@ -924,6 +933,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--upstream-identity-provider-flow", "browser_authcode",
|
"--upstream-identity-provider-flow", "browser_authcode",
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
||||||
@ -980,6 +990,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--upstream-identity-provider-flow", "browser_authcode",
|
"--upstream-identity-provider-flow", "browser_authcode",
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
// Run "kubectl get namespaces" which should trigger a browser login via the plugin.
|
||||||
@ -1036,6 +1047,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
|
|||||||
"--oidc-ca-bundle", testCABundlePath,
|
"--oidc-ca-bundle", testCABundlePath,
|
||||||
"--upstream-identity-provider-flow", "cli_password", // put cli_password in the kubeconfig, so we can override it with the env var
|
"--upstream-identity-provider-flow", "cli_password", // put cli_password in the kubeconfig, so we can override it with the env var
|
||||||
"--oidc-session-cache", sessionCachePath,
|
"--oidc-session-cache", sessionCachePath,
|
||||||
|
"--oidc-scopes", "offline_access,openid,pinniped:request-audience,groups",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Override the --upstream-identity-provider-flow flag from the kubeconfig using the env var.
|
// Override the --upstream-identity-provider-flow flag from the kubeconfig using the env var.
|
||||||
@ -1311,7 +1323,7 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
downstreamScopes := []string{coreosoidc.ScopeOfflineAccess, coreosoidc.ScopeOpenID, "pinniped:request-audience"}
|
downstreamScopes := []string{coreosoidc.ScopeOfflineAccess, coreosoidc.ScopeOpenID, "pinniped:request-audience", "groups"}
|
||||||
sort.Strings(downstreamScopes)
|
sort.Strings(downstreamScopes)
|
||||||
token := cache.GetToken(oidcclient.SessionCacheKey{
|
token := cache.GetToken(oidcclient.SessionCacheKey{
|
||||||
Issuer: downstream.Spec.Issuer,
|
Issuer: downstream.Spec.Issuer,
|
||||||
@ -1326,12 +1338,16 @@ func requireUserCanUseKubectlWithoutAuthenticatingAgain(
|
|||||||
idTokenClaims := token.IDToken.Claims
|
idTokenClaims := token.IDToken.Claims
|
||||||
require.Equal(t, expectedUsername, idTokenClaims[oidc.DownstreamUsernameClaim])
|
require.Equal(t, expectedUsername, idTokenClaims[oidc.DownstreamUsernameClaim])
|
||||||
|
|
||||||
|
if expectedGroups == nil {
|
||||||
|
require.Nil(t, idTokenClaims[oidc.DownstreamGroupsClaim])
|
||||||
|
} else {
|
||||||
// The groups claim in the file ends up as an []interface{}, so adjust our expectation to match.
|
// The groups claim in the file ends up as an []interface{}, so adjust our expectation to match.
|
||||||
expectedGroupsAsEmptyInterfaces := make([]interface{}, 0, len(expectedGroups))
|
expectedGroupsAsEmptyInterfaces := make([]interface{}, 0, len(expectedGroups))
|
||||||
for _, g := range expectedGroups {
|
for _, g := range expectedGroups {
|
||||||
expectedGroupsAsEmptyInterfaces = append(expectedGroupsAsEmptyInterfaces, g)
|
expectedGroupsAsEmptyInterfaces = append(expectedGroupsAsEmptyInterfaces, g)
|
||||||
}
|
}
|
||||||
require.ElementsMatch(t, expectedGroupsAsEmptyInterfaces, idTokenClaims[oidc.DownstreamGroupsClaim])
|
require.ElementsMatch(t, expectedGroupsAsEmptyInterfaces, idTokenClaims[oidc.DownstreamGroupsClaim])
|
||||||
|
}
|
||||||
|
|
||||||
expectedGroupsPlusAuthenticated := append([]string{}, expectedGroups...)
|
expectedGroupsPlusAuthenticated := append([]string{}, expectedGroups...)
|
||||||
expectedGroupsPlusAuthenticated = append(expectedGroupsPlusAuthenticated, "system:authenticated")
|
expectedGroupsPlusAuthenticated = append(expectedGroupsPlusAuthenticated, "system:authenticated")
|
||||||
|
@ -1381,7 +1381,7 @@ func testSupervisorLogin(
|
|||||||
ClientID: "pinniped-cli",
|
ClientID: "pinniped-cli",
|
||||||
Endpoint: discovery.Endpoint(),
|
Endpoint: discovery.Endpoint(),
|
||||||
RedirectURL: localCallbackServer.URL,
|
RedirectURL: localCallbackServer.URL,
|
||||||
Scopes: []string{"openid", "pinniped:request-audience", "offline_access"},
|
Scopes: []string{"openid", "pinniped:request-audience", "offline_access", "groups"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a valid downstream authorize URL for the supervisor.
|
// Build a valid downstream authorize URL for the supervisor.
|
||||||
@ -1416,7 +1416,7 @@ func testSupervisorLogin(
|
|||||||
t.Logf("got callback request: %s", testlib.MaskTokens(callback.URL.String()))
|
t.Logf("got callback request: %s", testlib.MaskTokens(callback.URL.String()))
|
||||||
if wantErrorType == "" {
|
if wantErrorType == "" {
|
||||||
require.Equal(t, stateParam.String(), callback.URL.Query().Get("state"))
|
require.Equal(t, stateParam.String(), callback.URL.Query().Get("state"))
|
||||||
require.ElementsMatch(t, []string{"openid", "pinniped:request-audience", "offline_access"}, strings.Split(callback.URL.Query().Get("scope"), " "))
|
require.ElementsMatch(t, []string{"openid", "pinniped:request-audience", "offline_access", "groups"}, strings.Split(callback.URL.Query().Get("scope"), " "))
|
||||||
authcode := callback.URL.Query().Get("code")
|
authcode := callback.URL.Query().Get("code")
|
||||||
require.NotEmpty(t, authcode)
|
require.NotEmpty(t, authcode)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user