diff --git a/api/authmethods/oidc_auth_method_authenticate_start_response.gen.go b/api/authmethods/oidc_auth_method_authenticate_start_response.gen.go index 8573fa600..e06876b2a 100644 --- a/api/authmethods/oidc_auth_method_authenticate_start_response.gen.go +++ b/api/authmethods/oidc_auth_method_authenticate_start_response.gen.go @@ -2,7 +2,6 @@ package authmethods type OidcAuthMethodAuthenticateStartResponse struct { - AuthUrl string `json:"auth_url,omitempty"` - TokenUrl string `json:"token_url,omitempty"` - TokenId string `json:"token_id,omitempty"` + AuthUrl string `json:"auth_url,omitempty"` + TokenId string `json:"token_id,omitempty"` } diff --git a/internal/auth/oidc/repository_auth_method_read.go b/internal/auth/oidc/repository_auth_method_read.go index 1701842aa..dde0f41ed 100644 --- a/internal/auth/oidc/repository_auth_method_read.go +++ b/internal/auth/oidc/repository_auth_method_read.go @@ -61,7 +61,7 @@ func (r *Repository) lookupAuthMethod(ctx context.Context, authMethodId string, // getAuthMethods allows the caller to either lookup a specific AuthMethod via // its id or search for a set AuthMethods within a set of scopes. Passing both -// scopeIds and a authMethodId is an error. The WithUnauthenticatedUser, +// scopeIds and a authMethod is an error. The WithUnauthenticatedUser, // WithLimit and WithOrder options are supported and all other options are // ignored. // diff --git a/internal/auth/oidc/service.go b/internal/auth/oidc/service.go index 082f12a03..c17002204 100644 --- a/internal/auth/oidc/service.go +++ b/internal/auth/oidc/service.go @@ -24,20 +24,15 @@ const ( // FinalRedirectEndpoint is the endpoint that the oidc callback redirect // client to after the callback is complete. - FinalRedirectEndpoint = "%s/authentication-complete" // TODO jimlambrt 2/2021 get redirect from FE before PR + FinalRedirectEndpoint = "%s/authentication-complete" // AuthenticationErrorsEndpoint is the endpoint that will returned as the final redirect // from the callback when there are auth errors - AuthenticationErrorsEndpoint = "%s/authentication-error" // TODO jimlambrt 3/2021 get endpoint from FE before PR + AuthenticationErrorsEndpoint = "%s/authentication-error" // CallbackEndpoint is the endpoint for the oidc callback which will be // included in the auth URL returned when an authen attempted is kicked off. - CallbackEndpoint = "%s/v1/auth-methods/%s:authenticate:callback" // TODO jimlambrt 2/2021 get endpoint from Todd before PR - - // TokenEndpoint is the endpoint the client will poll to see if their - // authentication attempt has completed and if successful, they'll - // retrieve their Boundary token. - TokenEndpoint = "%s/v1/auth-methods/%s:authenticate:token" // TODO jimlambrt 2/2021 get endpoint from Todd before PR + CallbackEndpoint = "%s/v1/auth-methods/%s:authenticate:callback" ) type ( @@ -162,7 +157,7 @@ func unwrapMessage(ctx context.Context, encodedWrappedMsg string) (*request.Wrap // both a Request.State and Request.Token. // // It first checks the cache of derived wrappers. If it's not found in the cache, it -// generates a key based on the scope's oidc DEK, using the scopeId and authMethodId +// generates a key based on the scope's oidc DEK, using the scopeId and authMethod // as salt and info for derivation, creates a wrapper for the new key, adds that wrapper // to the cache and the returns the wrapper for the new key. // @@ -193,7 +188,7 @@ func requestWrappingWrapper(ctx context.Context, k *kms.Kms, scopeId, authMethod return derivedWrapper.(*aead.Wrapper), nil } - // okay, I guess we need to derive a new key for this combo of oidcWrapper and authMethodId + // okay, I guess we need to derive a new key for this combo of oidcWrapper and authMethod reader, err := kms.NewDerivedReader(oidcWrapper, 32, []byte(authMethodId), []byte(scopeId)) if err != nil { return nil, errors.Wrap(err, op) diff --git a/internal/auth/oidc/service_callback.go b/internal/auth/oidc/service_callback.go index 841bbfcc1..79f92c84e 100644 --- a/internal/auth/oidc/service_callback.go +++ b/internal/auth/oidc/service_callback.go @@ -46,7 +46,7 @@ func Callback( oidcRepoFn OidcRepoFactory, iamRepoFn IamRepoFactory, atRepoFn AuthTokenRepoFactory, - authMethodId, + am *AuthMethod, state, code string) (finalRedirect string, e error) { const op = "oidc.Callback" if oidcRepoFn == nil { @@ -58,8 +58,8 @@ func Callback( if atRepoFn == nil { return "", errors.New(errors.InvalidParameter, op, "missing auth token repository function") } - if authMethodId == "" { - return "", errors.New(errors.InvalidParameter, op, "missing auth method id") + if am == nil { + return "", errors.New(errors.InvalidParameter, op, "missing auth method") } if state == "" { return "", errors.New(errors.InvalidParameter, op, "missing state") @@ -82,17 +82,10 @@ func Callback( if err != nil { return "", errors.Wrap(err, op) } - am, err := r.lookupAuthMethod(ctx, authMethodId) - if err != nil { - return "", errors.Wrap(err, op) - } - if am == nil { - return "", errors.New(errors.RecordNotFound, op, fmt.Sprintf("auth method %s not found", authMethodId)) - } // state is a proto request.Wrapper msg, which contains a cipher text field, so // we need the derived wrapper that was used to encrypt it. - requestWrapper, err := requestWrappingWrapper(ctx, r.kms, am.ScopeId, authMethodId) + requestWrapper, err := requestWrappingWrapper(ctx, r.kms, am.ScopeId, am.GetPublicId()) if err != nil { return "", errors.Wrap(err, op) } @@ -103,8 +96,8 @@ func Callback( // it appears the authentication request was started with one auth method // and the callback came to a different auth method. - if stateWrapper.AuthMethodId != authMethodId { - return "", errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s auth method id does not match request wrapper auth method id: %s", authMethodId, stateWrapper)) + if stateWrapper.AuthMethodId != am.GetPublicId() { + return "", errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s auth method id does not match request wrapper auth method id: %s", am.GetPublicId(), stateWrapper)) } stateBytes, err := decryptMessage(ctx, requestWrapper, stateWrapper) diff --git a/internal/auth/oidc/service_callback_test.go b/internal/auth/oidc/service_callback_test.go index fc46fbe8e..9114dc409 100644 --- a/internal/auth/oidc/service_callback_test.go +++ b/internal/auth/oidc/service_callback_test.go @@ -120,7 +120,6 @@ func Test_Callback(t *testing.T) { iamRepoFn IamRepoFactory // returns a new iam repo atRepoFn AuthTokenRepoFactory // returns a new auth token repo am *AuthMethod // the authmethod for the test - authMethodIdOverride *string // an override of the authmethod.PublcId as a callback parameters state string // state parameter for test provider and Callback(...) code string // code parameter for test provider and Callback(...) wantSubject string // sub claim from id token @@ -224,35 +223,23 @@ func Test_Callback(t *testing.T) { wantErrContains: "missing code", }, { - name: "missing-auth-method-id", + name: "missing-auth-method", oidcRepoFn: repoFn, iamRepoFn: iamRepoFn, atRepoFn: atRepoFn, - am: func() *AuthMethod { am := AllocAuthMethod(); return &am }(), + am: func() *AuthMethod { return nil }(), state: testState(t, testAuthMethod, kmsCache, testTokenRequestId, 2000*time.Second, "https://testcontroler.com/hi-alice", testConfigHash, testNonce), code: "simple", wantErrMatch: errors.T(errors.InvalidParameter), wantErrContains: "missing auth method", }, { - name: "bad-auth-method-id", - oidcRepoFn: repoFn, - iamRepoFn: iamRepoFn, - atRepoFn: atRepoFn, - am: func() *AuthMethod { am := AllocAuthMethod(); am.PublicId = "not-valid"; return &am }(), - state: testState(t, testAuthMethod, kmsCache, testTokenRequestId, 2000*time.Second, "https://testcontroler.com/hi-alice", testConfigHash, testNonce), - code: "simple", - wantErrMatch: errors.T(errors.RecordNotFound), - wantErrContains: "auth method not-valid not found", - }, - { - name: "mismatch-auth-method-id", // must remain the first test + name: "mismatch-auth-method", oidcRepoFn: repoFn, iamRepoFn: iamRepoFn, atRepoFn: atRepoFn, am: testAuthMethod, - authMethodIdOverride: &testAuthMethod2.PublicId, - state: testState(t, testAuthMethod, kmsCache, testTokenRequestId, 2000*time.Second, "https://testcontroler.com/hi-alice", testConfigHash, testNonce), + state: testState(t, testAuthMethod2, kmsCache, testTokenRequestId, 2000*time.Second, "https://testcontroler.com/hi-alice", testConfigHash, testNonce), code: "simple", wantErrMatch: errors.T(errors.InvalidParameter), wantErrContains: "auth method id does not match request wrapper auth method id", @@ -339,18 +326,12 @@ func Test_Callback(t *testing.T) { if len(info) > 0 { tp.SetUserInfoReply(info) } - var authMethodId string - if tt.authMethodIdOverride != nil { - authMethodId = *tt.authMethodIdOverride - } else { - authMethodId = tt.am.PublicId - } gotRedirect, err := Callback(ctx, tt.oidcRepoFn, tt.iamRepoFn, tt.atRepoFn, - authMethodId, + tt.am, tt.state, tt.code, ) @@ -370,7 +351,11 @@ func Test_Callback(t *testing.T) { var entries []oplog.Entry err = rw.SearchWhere(ctx, &entries, "1=?", []interface{}{1}) require.NoError(err) - require.Equalf(0, len(entries), "should not have found oplog entry for %s", tt.am.PublicId) + amId := "" + if tt.am != nil { + amId = tt.am.PublicId + } + require.Equalf(0, len(entries), "should not have found oplog entry for %s", amId) return } require.NoError(err) @@ -476,7 +461,7 @@ func Test_Callback(t *testing.T) { repoFn, iamRepoFn, atRepoFn, - testAuthMethod.PublicId, + testAuthMethod, state, "simple", ) @@ -488,7 +473,7 @@ func Test_Callback(t *testing.T) { repoFn, iamRepoFn, atRepoFn, - testAuthMethod.PublicId, + testAuthMethod, state, "simple", ) @@ -563,9 +548,9 @@ func Test_StartAuth_to_Callback(t *testing.T) { // the test controller is stateful and needs to know what auth method id // it's suppose to operate on - controller.SetAuthMethodId(endToEndAuthMethod.PublicId) + controller.SetAuthMethod(endToEndAuthMethod) - authUrl, _, _, err := StartAuth(ctx, repoFn, endToEndAuthMethod.PublicId) + authUrl, _, err := StartAuth(ctx, repoFn, endToEndAuthMethod.PublicId) require.NoError(err) authParams, err := url.ParseQuery(authUrl.RawQuery) diff --git a/internal/auth/oidc/service_start_auth.go b/internal/auth/oidc/service_start_auth.go index 9740e5d2c..9d9d89537 100644 --- a/internal/auth/oidc/service_start_auth.go +++ b/internal/auth/oidc/service_start_auth.go @@ -30,33 +30,33 @@ import ( // WithRoundTripPayload(string) provides an option for a client roundtrip // payload. This payload will be added to the final redirect as a query // parameter. -func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId string, opt ...Option) (authUrl *url.URL, tokenUrl *url.URL, tokenId string, e error) { +func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId string, opt ...Option) (authUrl *url.URL, tokenId string, e error) { const op = "oidc.StartAuth" if authMethodId == "" { - return nil, nil, "", errors.New(errors.InvalidParameter, op, "missing auth method id") + return nil, "", errors.New(errors.InvalidParameter, op, "missing auth method id") } if oidcRepoFn == nil { - return nil, nil, "", errors.New(errors.InvalidParameter, op, "missing oidc repo function") + return nil, "", errors.New(errors.InvalidParameter, op, "missing oidc repo function") } r, err := oidcRepoFn() if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } am, err := r.lookupAuthMethod(ctx, authMethodId) if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } if am == nil { - return nil, nil, "", errors.New(errors.RecordNotFound, op, fmt.Sprintf("auth method %s not found", authMethodId)) + return nil, "", errors.New(errors.RecordNotFound, op, fmt.Sprintf("auth method %s not found", authMethodId)) } if am.OperationalState == string(InactiveState) { - return nil, nil, "", errors.New(errors.AuthMethodInactive, op, "not allowed to start authentication attempt") + return nil, "", errors.New(errors.AuthMethodInactive, op, "not allowed to start authentication attempt") } // get the provider from the cache (if possible) provider, err := providerCache().get(ctx, am) if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } callbackRedirect := fmt.Sprintf(CallbackEndpoint, am.GetApiUrl(), authMethodId) @@ -72,15 +72,15 @@ func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId str exp := timestamppb.New(now.Add(AttemptExpiration).Truncate(time.Second)) tokenRequestId, err := authtoken.NewAuthTokenId() if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } nonce, err := oidc.NewID() if err != nil { - return nil, nil, "", errors.New(errors.Unknown, op, "unable to generate nonce", errors.WithWrap(err)) + return nil, "", errors.New(errors.Unknown, op, "unable to generate nonce", errors.WithWrap(err)) } hash, err := provider.ConfigHash() if err != nil { - return nil, nil, "", errors.New(errors.Unknown, op, "unable to get provider config hash", errors.WithWrap(err)) + return nil, "", errors.New(errors.Unknown, op, "unable to get provider config hash", errors.WithWrap(err)) } st := &request.State{ TokenRequestId: tokenRequestId, @@ -93,11 +93,11 @@ func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId str requestWrapper, err := requestWrappingWrapper(ctx, r.kms, am.ScopeId, authMethodId) if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } encodedEncryptedSt, err := encryptMessage(ctx, requestWrapper, am, st) if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } oidcOpts := []oidc.Option{ oidc.WithState(string(encodedEncryptedSt)), @@ -119,16 +119,16 @@ func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId str callbackRedirect, oidcOpts...) if err != nil { - return nil, nil, "", errors.New(errors.Unknown, op, "unable to create oidc request", errors.WithWrap(err)) + return nil, "", errors.New(errors.Unknown, op, "unable to create oidc request", errors.WithWrap(err)) } u, err := provider.AuthURL(ctx, oidcReq) if err != nil { - return nil, nil, "", errors.New(errors.Unknown, op, "unable to get auth url from provider", errors.WithWrap(err)) + return nil, "", errors.New(errors.Unknown, op, "unable to get auth url from provider", errors.WithWrap(err)) } authUrl, err = url.Parse(u) if err != nil { - return nil, nil, "", errors.New(errors.Unknown, op, "unable to parse auth url", errors.WithWrap(err)) + return nil, "", errors.New(errors.Unknown, op, "unable to parse auth url", errors.WithWrap(err)) } t := &request.Token{ @@ -137,12 +137,7 @@ func StartAuth(ctx context.Context, oidcRepoFn OidcRepoFactory, authMethodId str } encodedEncryptedTk, err := encryptMessage(ctx, requestWrapper, am, t) if err != nil { - return nil, nil, "", errors.Wrap(err, op) + return nil, "", errors.Wrap(err, op) } - tokenUrl, err = url.Parse(fmt.Sprintf(TokenEndpoint, am.GetApiUrl(), authMethodId)) - if err != nil { - return nil, nil, "", errors.New(errors.Unknown, op, "unable to generate token URL", errors.WithWrap(err)) - } - - return authUrl, tokenUrl, encodedEncryptedTk, nil + return authUrl, encodedEncryptedTk, nil } diff --git a/internal/auth/oidc/service_start_auth_test.go b/internal/auth/oidc/service_start_auth_test.go index e46234867..f7095549d 100644 --- a/internal/auth/oidc/service_start_auth_test.go +++ b/internal/auth/oidc/service_start_auth_test.go @@ -158,18 +158,16 @@ func Test_StartAuth(t *testing.T) { } now := time.Now() - authUrl, tokenUrl, tokenId, err := StartAuth(ctx, tt.repoFn, tt.authMethod.PublicId, WithRoundtripPayload(tt.roundTripper)) + authUrl, tokenId, err := StartAuth(ctx, tt.repoFn, tt.authMethod.PublicId, WithRoundtripPayload(tt.roundTripper)) if tt.wantErrMatch != nil { require.Error(err) assert.Nil(authUrl) - assert.Nil(tokenUrl) assert.Truef(errors.Match(tt.wantErrMatch, err), "want err code: %q got: %q", tt.wantErrMatch, err) assert.Contains(err.Error(), tt.wantErrContains) return } require.NoError(err) require.NotNil(authUrl) - require.NotNil(tokenUrl) authParams, err := url.ParseQuery(authUrl.RawQuery) require.NoError(err) diff --git a/internal/auth/oidc/testing.go b/internal/auth/oidc/testing.go index 714583fd2..10b88fea4 100644 --- a/internal/auth/oidc/testing.go +++ b/internal/auth/oidc/testing.go @@ -339,12 +339,12 @@ func TestPendingToken( type testControllerSrv struct { mu sync.RWMutex - authMethodId string - oidcRepoFn OidcRepoFactory - iamRepoFn IamRepoFactory - atRepoFn AuthTokenRepoFactory - httpServer *httptest.Server - t *testing.T + authMethod *AuthMethod + oidcRepoFn OidcRepoFactory + iamRepoFn IamRepoFactory + atRepoFn AuthTokenRepoFactory + httpServer *httptest.Server + t *testing.T } // startTestControllerSrv returns a running testControllerSrv @@ -365,22 +365,22 @@ func startTestControllerSrv(t *testing.T, oidcRepoFn OidcRepoFactory, iamRepoFn return s } -// SetAuthMethodId will set the auth method id used for various +// SetAuthMethod will set the auth method id used for various // operations -func (s *testControllerSrv) SetAuthMethodId(authMethodId string) { +func (s *testControllerSrv) SetAuthMethod(authMethod *AuthMethod) { s.t.Helper() s.mu.Lock() defer s.mu.Unlock() - s.authMethodId = authMethodId + s.authMethod = authMethod } -// AuthMethodId returns the auth method id currently being used +// AuthMethod returns the auth method id currently being used // for various operations -func (s *testControllerSrv) AuthMethodId() string { +func (s *testControllerSrv) AuthMethod() *AuthMethod { s.t.Helper() s.mu.RLock() defer s.mu.RUnlock() - return s.authMethodId + return s.authMethod } // Addr returns the scheme.host.port of the running srv. @@ -401,8 +401,8 @@ func (s *testControllerSrv) FinalRedirectUrl() string { func (s *testControllerSrv) CallbackUrl() string { s.t.Helper() require := require.New(s.t) - require.NotEmptyf(s.authMethodId, "auth method id was missing") - return fmt.Sprintf(CallbackEndpoint, s.Addr(), s.authMethodId) + require.NotNil(s.authMethod, "auth method was missing") + return fmt.Sprintf(CallbackEndpoint, s.Addr(), s.authMethod.GetPublicId()) } // ServeHTTP satisfies the http.Handler interface @@ -410,7 +410,7 @@ func (s *testControllerSrv) ServeHTTP(w http.ResponseWriter, req *http.Request) s.t.Helper() require := require.New(s.t) switch req.URL.Path { - case fmt.Sprintf("/v1/auth-methods/%s:authenticate:callback", s.authMethodId): + case fmt.Sprintf("/v1/auth-methods/%s:authenticate:callback", s.authMethod.GetPublicId()): err := req.ParseForm() require.NoErrorf(err, "%s: internal error: %w", "callback", err) state := req.FormValue("state") @@ -427,7 +427,7 @@ func (s *testControllerSrv) ServeHTTP(w http.ResponseWriter, req *http.Request) s.oidcRepoFn, s.iamRepoFn, s.atRepoFn, - s.authMethodId, + s.authMethod, state, code, ) diff --git a/internal/proto/local/controller/api/resources/authmethods/v1/auth_method.proto b/internal/proto/local/controller/api/resources/authmethods/v1/auth_method.proto index 3d9a9938f..31c43cd8c 100644 --- a/internal/proto/local/controller/api/resources/authmethods/v1/auth_method.proto +++ b/internal/proto/local/controller/api/resources/authmethods/v1/auth_method.proto @@ -134,9 +134,6 @@ message OidcAuthMethodAuthenticateStartResponse { // The returned authentication URL string auth_url = 10 [json_name="auth_url"]; - // The returned token URL - string token_url = 20 [json_name="token_url"]; - // The returned token ID string token_id = 30 [json_name="token_id"]; } diff --git a/internal/servers/controller/handlers/authmethods/oidc.go b/internal/servers/controller/handlers/authmethods/oidc.go index 5b3964245..d47a33046 100644 --- a/internal/servers/controller/handlers/authmethods/oidc.go +++ b/internal/servers/controller/handlers/authmethods/oidc.go @@ -150,9 +150,9 @@ func (s Service) authenticateOidc(ctx context.Context, req *pbs.AuthenticateRequ } switch req.GetCommand() { case startCommand: - return s.authenticateOidcStart(ctx, req, authResults) + return s.authenticateOidcStart(ctx, req) case callbackCommand: - return s.authenticateOidcCallback(ctx, req, authResults) + return s.authenticateOidcCallback(ctx, req) case tokenCommand: return s.authenticateOidcToken(ctx, req, authResults) } @@ -160,79 +160,106 @@ func (s Service) authenticateOidc(ctx context.Context, req *pbs.AuthenticateRequ return &pbs.AuthenticateResponse{Command: req.GetCommand(), Attributes: nil}, nil } -func (s Service) authenticateOidcStart(ctx context.Context, req *pbs.AuthenticateRequest, authResults *auth.VerifyResults) (*pbs.AuthenticateResponse, error) { +func (s Service) authenticateOidcStart(ctx context.Context, req *pbs.AuthenticateRequest) (*pbs.AuthenticateResponse, error) { const op = "authmethod_service.(Service).authenticateOidcStart" if req == nil { return nil, errors.New(errors.InvalidParameter, op, "Nil request.") } - if authResults == nil { - return nil, errors.New(errors.InvalidParameter, op, "Nil auth results.") - } var opts []oidc.Option - if req.GetAttributes() != nil { - attrs := new(pbs.OidcStartAttributes) - if err := handlers.StructToProto(req.GetAttributes(), attrs); err != nil { - return nil, errors.New(errors.InvalidParameter, op, "Error parsing request attributes.") - } - if attrs.GetCachedRoundtripPayload() != "" { - opts = append(opts, oidc.WithRoundtripPayload(attrs.GetCachedRoundtripPayload())) - } + attrs := new(pbs.OidcStartAttributes) + if err := handlers.StructToProto(req.GetAttributes(), attrs); err != nil { + return nil, errors.New(errors.InvalidParameter, op, "Error parsing request attributes.") + } + if attrs.GetCachedRoundtripPayload() != "" { + opts = append(opts, oidc.WithRoundtripPayload(attrs.GetCachedRoundtripPayload())) } - authUrl, tokenUrl, tokenId, err := oidc.StartAuth(ctx, s.oidcRepoFn, req.GetAuthMethodId(), opts...) + authUrl, tokenId, err := oidc.StartAuth(ctx, s.oidcRepoFn, req.GetAuthMethodId(), opts...) if err != nil { // TODO: Log something return nil, errors.New(errors.Internal, op, "Error generating parameters for starting the OIDC flow.") } - resp := &pb.OidcAuthMethodAuthenticateStartResponse{ - AuthUrl: authUrl.String(), - TokenUrl: tokenUrl.String(), - TokenId: tokenId, + respAttrs := &pb.OidcAuthMethodAuthenticateStartResponse{ + AuthUrl: authUrl.String(), + TokenId: tokenId, } - - attrs, err := handlers.ProtoToStruct(resp) - if err != nil { + resp := &pbs.AuthenticateResponse{Command: req.GetCommand()} + if resp.Attributes, err = handlers.ProtoToStruct(respAttrs); err != nil { return nil, errors.New(errors.Internal, op, "Error marshaling parameters.") } - - return &pbs.AuthenticateResponse{Command: req.GetCommand(), Attributes: attrs}, nil + return resp, nil } -func (s Service) authenticateOidcCallback(ctx context.Context, req *pbs.AuthenticateRequest, authResults *auth.VerifyResults) (*pbs.AuthenticateResponse, error) { +// authenticateOidcCallback behaves differently than other service methods. +// Because of the way it this is called by the end user, it should only return +// an error if we are unable to lookup the auth method or the request +// parameters were invalid. All other errors should be returned back through +// the response as a finalRedirectUrl to an endpoint that can properly show the +// error details. +func (s Service) authenticateOidcCallback(ctx context.Context, req *pbs.AuthenticateRequest) (*pbs.AuthenticateResponse, error) { const op = "authmethod_service.(Service).authenticateOidcCallback" if req == nil { return nil, errors.New(errors.InvalidParameter, op, "Nil request.") } - if authResults == nil { - return nil, errors.New(errors.InvalidParameter, op, "Nil auth results.") + + repo, err := s.oidcRepoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + am, err := repo.LookupAuthMethod(ctx, req.GetAuthMethodId()) + if err != nil { + return nil, errors.Wrap(err, op) + } + if am == nil { + return nil, errors.New(errors.RecordNotFound, op, fmt.Sprintf("auth method %s not found", req.GetAuthMethodId())) + } + if am.GetApiUrl() == "" { + return nil, errors.New(errors.InvalidParameter, op, "auth method doesn't have api url defined") + } + + errRedirectBase := fmt.Sprintf(oidc.AuthenticationErrorsEndpoint, am.GetApiUrl()) + errResponse := func(err error) *pbs.AuthenticateResponse { + u := make(url.Values) + // TODO: Decide how to format the domain error to match OIDC error format + u.Add("error", err.Error()) + errRedirect := fmt.Sprintf("%s?%s", errRedirectBase, u.Encode()) + return &pbs.AuthenticateResponse{Command: callbackCommand, Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "final_redirect_url": structpb.NewStringValue(errRedirect), + }, + }} } attrs := new(pb.OidcAuthMethodAuthenticateCallbackRequest) // Note that this conversion has already happened in the validate call so we don't expect errors here. if err := handlers.StructToProto(req.GetAttributes(), attrs, handlers.WithDiscardUnknownFields(true)); err != nil { - return nil, err + return errResponse(err), nil } - finalRedirectUrl, err := oidc.Callback( + var finalRedirectUrl string + if attrs.GetError() != "" { + // TODO: Package the OIDC error into the redirectUrl + return errResponse(fmt.Errorf("Error: %q, Details: %q", attrs.GetError(), attrs.GetErrorDescription())), nil + } + finalRedirectUrl, err = oidc.Callback( ctx, s.oidcRepoFn, oidc.IamRepoFactory(s.iamRepoFn), s.atRepoFn, - req.GetAuthMethodId(), + am, attrs.GetState(), attrs.GetCode()) if err != nil { - // TODO: Log something more meaningful - return nil, errors.New(errors.InvalidParameter, op, "Callback validation failed.", errors.WithWrap(err)) + return errResponse(errors.New(errors.InvalidParameter, op, "Callback validation failed.", errors.WithWrap(err))), nil } respAttrs, err := handlers.ProtoToStruct(&pb.OidcAuthMethodAuthenticateCallbackResponse{ FinalRedirectUrl: finalRedirectUrl, }) if err != nil { - return nil, errors.New(errors.Internal, op, "Error marshaling parameters") + return errResponse(errors.New(errors.Internal, op, "Error marshaling parameters")), nil } return &pbs.AuthenticateResponse{Command: req.GetCommand(), Attributes: respAttrs}, nil @@ -339,12 +366,7 @@ func validateAuthenticateOidcRequest(req *pbs.AuthenticateRequest) error { break } - if attrs.GetError() != "" { - // TODO: Log more info. - return handlers.ApiErrorWithCodeAndMessage(codes.Unauthenticated, "OIDC provider returned an error.") - } - - if attrs.GetCode() == "" { + if attrs.GetCode() == "" && attrs.GetError() == "" { badFields[codeField] = "Code field not supplied in callback request." } diff --git a/internal/servers/controller/handlers/authmethods/oidc_test.go b/internal/servers/controller/handlers/authmethods/oidc_test.go index 03b765e4a..5b85a1dad 100644 --- a/internal/servers/controller/handlers/authmethods/oidc_test.go +++ b/internal/servers/controller/handlers/authmethods/oidc_test.go @@ -1373,8 +1373,6 @@ func TestAuthenticate_OIDC_Start(t *testing.T) { require.NotNil(m) require.Contains(m, "auth_url") require.NotEmpty(m["auth_url"]) - require.Contains(m, "token_url") - require.NotEmpty(m["token_url"]) require.Contains(m, "token_id") require.NotEmpty(m["token_id"]) }) diff --git a/website/content/docs/concepts/security/permissions.mdx b/website/content/docs/concepts/security/permissions.mdx index 0760cb557..a62a98afc 100644 --- a/website/content/docs/concepts/security/permissions.mdx +++ b/website/content/docs/concepts/security/permissions.mdx @@ -214,11 +214,11 @@ wildcard or templated grant strings.
account
- account
+ create: Create an account
type=<type>;actions=createtype=<type>;actions=create
+ list: List accounts
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ <auth-method-id>
- <auth-method-id>
+ account
- account
+ read: Read an account
id=<id>;actions=readid=<pin>;type=<type>;actions=readid=<id>;actions=read
+ id=<pin>;type=<type>;actions=read
+ update: Update an account
id=<id>;actions=updateid=<pin>;type=<type>;actions=updateid=<id>;actions=update
+ id=<pin>;type=<type>;actions=update
+ delete: Delete an account
id=<id>;actions=deleteid=<pin>;type=<type>;actions=deleteid=<id>;actions=delete
+ id=<pin>;type=<type>;actions=delete
+ set-password: Set a password on an account, without requiring the current password
+ set-password: Set a password on an account, without
+ requiring the current password
id=<id>;actions=set-passwordid=<pin>;type=<type>;actions=set-passwordid=<id>;actions=set-password
+ id=<pin>;type=<type>;actions=set-password
+ change-password: Change a password on an account given the current password
+ change-password: Change a password on an account given
+ the current password
id=<id>;actions=change-passwordid=<pin>;type=<type>;actions=change-passwordid=<id>;actions=change-password
+
+ id=<pin>;type=<type>;actions=change-password
+
+ auth-method
- auth-method
+ create: Create an auth method
type=<type>;actions=createtype=<type>;actions=create
+ list: List auth methods
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ auth-method
- auth-method
+ read: Read an auth method
id=<id>;actions=readid=<id>;actions=read
+ update: Update an auth method
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete an auth method
id=<id>;actions=deleteid=<id>;actions=delete
+ authenticate: Authenticate to an auth method
id=<id>;actions=authenticateid=<id>;actions=authenticate
+ auth-token
- auth-token
+ list: List auth tokens
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ auth-token
- auth-token
+ read: Read an auth token
id=<id>;actions=readid=<id>;actions=read
+ delete: Delete an auth token
id=<id>;actions=deleteid=<id>;actions=delete
+ group
- group
+ create: Create a group
type=<type>;actions=createtype=<type>;actions=create
+ list: List groups
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ group
- group
+ read: Read a group
id=<id>;actions=readid=<id>;actions=read
+ update: Update a group
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete a group
id=<id>;actions=deleteid=<id>;actions=delete
+ add-members: Add members to a group
id=<id>;actions=add-membersid=<id>;actions=add-members
+ set-members: Set the full set of members on a group
id=<id>;actions=set-membersid=<id>;actions=set-members
+ remove-members: Remove members from a group
id=<id>;actions=remove-membersid=<id>;actions=remove-members
+ host
- host
+ create: Create a host
type=<type>;actions=createtype=<type>;actions=create
+ list: List hosts
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ <host-catalog-id>
- <host-catalog-id>
+ host
- host
+ read: Read a host
id=<id>;actions=readid=<pin>;type=<type>;actions=readid=<id>;actions=read
+ id=<pin>;type=<type>;actions=read
+ update: Update a host
id=<id>;actions=updateid=<pin>;type=<type>;actions=updateid=<id>;actions=update
+ id=<pin>;type=<type>;actions=update
+ delete: Delete a host
id=<id>;actions=deleteid=<pin>;type=<type>;actions=deleteid=<id>;actions=delete
+ id=<pin>;type=<type>;actions=delete
+ host-catalog
- host-catalog
+ create: Create a host catalog
type=<type>;actions=createtype=<type>;actions=create
+ list: List host catalogs
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ host-catalog
- host-catalog
+ read: Read a host catalog
id=<id>;actions=readid=<id>;actions=read
+ update: Update a host catalog
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete a host catalog
id=<id>;actions=deleteid=<id>;actions=delete
+ host-set
- host-set
+ create: Create a host set
type=<type>;actions=createtype=<type>;actions=create
+ list: List host sets
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ <host-catalog-id>
- <host-catalog-id>
+ host-set
- host-set
+ read: Read a host set
id=<id>;actions=readid=<pin>;type=<type>;actions=readid=<id>;actions=read
+ id=<pin>;type=<type>;actions=read
+ update: Update a host set
id=<id>;actions=updateid=<pin>;type=<type>;actions=updateid=<id>;actions=update
+ id=<pin>;type=<type>;actions=update
+ delete: Delete a host set
id=<id>;actions=deleteid=<pin>;type=<type>;actions=deleteid=<id>;actions=delete
+ id=<pin>;type=<type>;actions=delete
+ add-hosts: Add hosts to a host-set
id=<id>;actions=add-hostsid=<pin>;type=<type>;actions=add-hostsid=<id>;actions=add-hosts
+ id=<pin>;type=<type>;actions=add-hosts
+ set-hosts: Set the full set of hosts on a host set
id=<id>;actions=set-hostsid=<pin>;type=<type>;actions=set-hostsid=<id>;actions=set-hosts
+ id=<pin>;type=<type>;actions=set-hosts
+ remove-hosts: Remove hosts from a host set
id=<id>;actions=remove-hostsid=<pin>;type=<type>;actions=remove-hostsid=<id>;actions=remove-hosts
+ id=<pin>;type=<type>;actions=remove-hosts
+ role
- role
+ create: Create a role
type=<type>;actions=createtype=<type>;actions=create
+ list: List roles
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ role
- role
+ read: Read a role
id=<id>;actions=readid=<id>;actions=read
+ update: Update a role
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete a role
id=<id>;actions=deleteid=<id>;actions=delete
+ add-principals: Add principals to a role
id=<id>;actions=add-principalsid=<id>;actions=add-principals
+ set-principals: Set the full set of principals on a role
+ set-principals: Set the full set of principals on a
+ role
id=<id>;actions=set-principalsid=<id>;actions=set-principals
+ remove-principals: Remove principals from a role
id=<id>;actions=remove-principalsid=<id>;actions=remove-principals
+ add-grants: Add grants to a role
id=<id>;actions=add-grantsid=<id>;actions=add-grants
+ set-grants: Set the full set of grants on a role
id=<id>;actions=set-grantsid=<id>;actions=set-grants
+ remove-grants: Remove grants from a role
id=<id>;actions=remove-grantsid=<id>;actions=remove-grants
+ scope
- scope
+ create: Create a scope
type=<type>;actions=createtype=<type>;actions=create
+ list: List scopes
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ scope
- scope
+ read: Read a scope
id=<id>;actions=readid=<id>;actions=read
+ update: Update a scope
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete a scope
id=<id>;actions=deleteid=<id>;actions=delete
+ session
- session
+ list: List sessions
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ session
- session
+ read: Read a session
id=<id>;actions=readid=<id>;actions=read
+ cancel: Cancel a session
id=<id>;actions=cancelid=<id>;actions=cancel
+ read:self: Read a session, which must be associated with the calling user
+ read:self: Read a session, which must be associated
+ with the calling user
id=*;type=session;actions=read:selfid=*;type=session;actions=read:self
+ cancel:self: Cancel a session, which must be associated with the calling user
+ cancel:self: Cancel a session, which must be associated
+ with the calling user
id=*;type=session;actions=cancel:selfid=*;type=session;actions=cancel:self
+ target
- target
+ create: Create a target
type=<type>;actions=createtype=<type>;actions=create
+ list: List targets
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ target
- target
+ read: Read a target
id=<id>;actions=readid=<id>;actions=read
+ update: Update a target
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete a target
id=<id>;actions=deleteid=<id>;actions=delete
+ add-host-sets: Add host sets to a target
id=<id>;actions=add-host-setsid=<id>;actions=add-host-sets
+ set-host-sets: Set the full set of host sets on a target
+ set-host-sets: Set the full set of host sets on a
+ target
id=<id>;actions=set-host-setsid=<id>;actions=set-host-sets
+ remove-host-sets: Remove host sets from a target
id=<id>;actions=remove-host-setsid=<id>;actions=remove-host-sets
+ authorize-session: Authorize a session via the target
id=<id>;actions=authorize-sessionid=<id>;actions=authorize-session
+ user
- user
+ create: Create a user
type=<type>;actions=createtype=<type>;actions=create
+ list: List users
type=<type>;actions=listtype=<type>;actions=list
+ <id>
- <id>
+ user
- user
+ read: Read a user
id=<id>;actions=readid=<id>;actions=read
+ update: Update a user
id=<id>;actions=updateid=<id>;actions=update
+ delete: Delete a user
id=<id>;actions=deleteid=<id>;actions=delete
+ add-accounts: Add accounts to a user
id=<id>;actions=add-accountsid=<id>;actions=add-accounts
+ set-accounts: Set the full set of accounts on a user
id=<id>;actions=set-accountsid=<id>;actions=set-accounts
+ remove-accounts: Remove accounts from a user
id=<id>;actions=remove-accountsid=<id>;actions=remove-accounts
+