mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-06 13:36:54 +00:00
First draft of implementation of multiple IDPs support
This commit is contained in:
@@ -23,13 +23,14 @@ import (
|
||||
oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
|
||||
"go.pinniped.dev/internal/httputil/httperr"
|
||||
"go.pinniped.dev/internal/oidc/provider"
|
||||
"go.pinniped.dev/internal/oidc/provider/upstreamprovider"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||
"go.pinniped.dev/pkg/oidcclient/pkce"
|
||||
)
|
||||
|
||||
func New(config *oauth2.Config, provider *coreosoidc.Provider, client *http.Client) provider.UpstreamOIDCIdentityProviderI {
|
||||
func New(config *oauth2.Config, provider *coreosoidc.Provider, client *http.Client) upstreamprovider.UpstreamOIDCIdentityProviderI {
|
||||
return &ProviderConfig{Config: config, Provider: provider, Client: client}
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ type ProviderConfig struct {
|
||||
}
|
||||
}
|
||||
|
||||
var _ provider.UpstreamOIDCIdentityProviderI = (*ProviderConfig)(nil)
|
||||
var _ upstreamprovider.UpstreamOIDCIdentityProviderI = (*ProviderConfig)(nil)
|
||||
|
||||
func (p *ProviderConfig) GetResourceUID() types.UID {
|
||||
return p.ResourceUID
|
||||
@@ -160,7 +161,7 @@ func (p *ProviderConfig) PerformRefresh(ctx context.Context, refreshToken string
|
||||
// It may return an error wrapped by a RetryableRevocationError, which is an error indicating that it may
|
||||
// be worth trying to revoke the same token again later. Any other error returned should be assumed to
|
||||
// represent an error such that it is not worth retrying revocation later, even though revocation failed.
|
||||
func (p *ProviderConfig) RevokeToken(ctx context.Context, token string, tokenType provider.RevocableTokenType) error {
|
||||
func (p *ProviderConfig) RevokeToken(ctx context.Context, token string, tokenType upstreamprovider.RevocableTokenType) error {
|
||||
if p.RevocationURL == nil {
|
||||
plog.Trace("RevokeToken() was called but upstream provider has no available revocation endpoint",
|
||||
"providerName", p.Name,
|
||||
@@ -188,7 +189,7 @@ func (p *ProviderConfig) RevokeToken(ctx context.Context, token string, tokenTyp
|
||||
func (p *ProviderConfig) tryRevokeToken(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
tokenType provider.RevocableTokenType,
|
||||
tokenType upstreamprovider.RevocableTokenType,
|
||||
useBasicAuth bool,
|
||||
) (tryAnotherClientAuthMethod bool, err error) {
|
||||
clientID := p.Config.ClientID
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"go.pinniped.dev/internal/mocks/mockkeyset"
|
||||
"go.pinniped.dev/internal/oidc/provider"
|
||||
"go.pinniped.dev/internal/oidc/provider/upstreamprovider"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||
@@ -484,7 +485,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
t.Run("RevokeToken", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tokenType provider.RevocableTokenType
|
||||
tokenType upstreamprovider.RevocableTokenType
|
||||
nilRevocationURL bool
|
||||
unreachableServer bool
|
||||
returnStatusCodes []int
|
||||
@@ -496,33 +497,33 @@ func TestProviderConfig(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "success without calling the server when there is no revocation URL set for refresh token",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
nilRevocationURL: true,
|
||||
wantNumRequests: 0,
|
||||
},
|
||||
{
|
||||
name: "success without calling the server when there is no revocation URL set for access token",
|
||||
tokenType: provider.AccessTokenType,
|
||||
tokenType: upstreamprovider.AccessTokenType,
|
||||
nilRevocationURL: true,
|
||||
wantNumRequests: 0,
|
||||
},
|
||||
{
|
||||
name: "success when the server returns 200 OK on the first call for refresh token",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusOK},
|
||||
wantNumRequests: 1,
|
||||
wantTokenTypeHint: "refresh_token",
|
||||
},
|
||||
{
|
||||
name: "success when the server returns 200 OK on the first call for access token",
|
||||
tokenType: provider.AccessTokenType,
|
||||
tokenType: upstreamprovider.AccessTokenType,
|
||||
returnStatusCodes: []int{http.StatusOK},
|
||||
wantNumRequests: 1,
|
||||
wantTokenTypeHint: "access_token",
|
||||
},
|
||||
{
|
||||
name: "success when the server returns 400 Bad Request on the first call due to client auth, then 200 OK on second call for refresh token",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest, http.StatusOK},
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 defines this as the error for client auth failure
|
||||
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`},
|
||||
@@ -531,7 +532,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "success when the server returns 400 Bad Request on the first call due to client auth, then 200 OK on second call for access token",
|
||||
tokenType: provider.AccessTokenType,
|
||||
tokenType: upstreamprovider.AccessTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest, http.StatusOK},
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 defines this as the error for client auth failure
|
||||
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`},
|
||||
@@ -540,7 +541,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when the server returns 400 Bad Request on the first call due to client auth, then any 400 error on second call",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest, http.StatusBadRequest},
|
||||
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`, `{ "error":"anything", "error_description":"unhappy" }`},
|
||||
wantErr: testutil.WantExactErrorString(`server responded with status 400 with body: { "error":"anything", "error_description":"unhappy" }`),
|
||||
@@ -550,7 +551,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when the server returns 400 Bad Request with bad JSON body on the first call",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest},
|
||||
returnErrBodies: []string{`invalid JSON body`},
|
||||
wantErr: testutil.WantExactErrorString(`error parsing response body "invalid JSON body" on response with status code 400: invalid character 'i' looking for beginning of value`),
|
||||
@@ -560,7 +561,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when the server returns 400 Bad Request with empty body",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest},
|
||||
returnErrBodies: []string{``},
|
||||
wantErr: testutil.WantExactErrorString(`error parsing response body "" on response with status code 400: unexpected end of JSON input`),
|
||||
@@ -570,7 +571,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when the server returns 400 Bad Request on the first call due to client auth, then any other error on second call",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest, http.StatusForbidden},
|
||||
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`, ""},
|
||||
wantErr: testutil.WantExactErrorString("server responded with status 403"),
|
||||
@@ -580,7 +581,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when server returns any other 400 error on first call",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest},
|
||||
returnErrBodies: []string{`{ "error":"anything_else", "error_description":"unhappy" }`},
|
||||
wantErr: testutil.WantExactErrorString(`server responded with status 400 with body: { "error":"anything_else", "error_description":"unhappy" }`),
|
||||
@@ -590,7 +591,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error when server returns any other error aside from 400 on first call",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusForbidden},
|
||||
returnErrBodies: []string{""},
|
||||
wantErr: testutil.WantExactErrorString("server responded with status 403"),
|
||||
@@ -600,7 +601,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "retryable error when server returns 503 on first call",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusServiceUnavailable}, // 503
|
||||
returnErrBodies: []string{""},
|
||||
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 503"),
|
||||
@@ -610,7 +611,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "retryable error when the server returns 400 Bad Request on the first call due to client auth, then 503 on second call",
|
||||
tokenType: provider.AccessTokenType,
|
||||
tokenType: upstreamprovider.AccessTokenType,
|
||||
returnStatusCodes: []int{http.StatusBadRequest, http.StatusServiceUnavailable}, // 400, 503
|
||||
returnErrBodies: []string{`{ "error":"invalid_client", "error_description":"unhappy" }`, ""},
|
||||
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 503"),
|
||||
@@ -620,7 +621,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "retryable error when server returns any 5xx status on first call, testing lower bound of 5xx range",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{http.StatusInternalServerError}, // 500
|
||||
returnErrBodies: []string{""},
|
||||
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 500"),
|
||||
@@ -630,7 +631,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "retryable error when server returns any 5xx status on first call, testing upper bound of 5xx range",
|
||||
tokenType: provider.RefreshTokenType,
|
||||
tokenType: upstreamprovider.RefreshTokenType,
|
||||
returnStatusCodes: []int{599}, // not defined by an RFC, but sometimes considered Network Connect Timeout Error
|
||||
returnErrBodies: []string{""},
|
||||
wantErr: testutil.WantExactErrorString("retryable revocation error: server responded with status 599"),
|
||||
@@ -640,7 +641,7 @@ func TestProviderConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "retryable error when the server cannot be reached",
|
||||
tokenType: provider.AccessTokenType,
|
||||
tokenType: upstreamprovider.AccessTokenType,
|
||||
unreachableServer: true,
|
||||
wantErr: testutil.WantMatchingErrorString("^retryable revocation error: Post .*: dial tcp .*: connect: connection refused$"),
|
||||
wantRetryableErrType: true,
|
||||
|
||||
Reference in New Issue
Block a user