mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-14 11:58:02 +00:00
5fd2061770
# Which Problems Are Solved Currently the OIDC API of ZITADEL only prints parent errors to the logs. Where 4xx status are typically warn level and 5xx error level. This makes it hard to debug certain errors for client in multi-instance environments like ZITADEL cloud, where there is no direct access to logs. In case of support requests we often can't correlate past log-lines to the error that was reported. This change adds the possibility to return the parent error in the response to the OIDC client. For the moment this only applies to JSON body responses, not error redirects to the RP. # How the Problems Are Solved - New instance-level feature flag: `debug_oidc_parent_error` - Use the new `WithReturnParentToClient()` function from the oidc lib introduced in https://github.com/zitadel/oidc/pull/629 for all cases where `WithParent` was already used and the request context is available. # Additional Changes none # Additional Context - Depends on: https://github.com/zitadel/oidc/pull/629 - Related to: https://github.com/zitadel/zitadel/issues/8362 --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
156 lines
5.3 KiB
Go
156 lines
5.3 KiB
Go
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
"github.com/zitadel/oidc/v3/pkg/op"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
"github.com/zitadel/zitadel/internal/query"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type clientCredentialsRequest struct {
|
|
sub string
|
|
audience []string
|
|
scopes []string
|
|
}
|
|
|
|
// GetSubject returns the subject for token to be created because of the client credentials request
|
|
// the subject will be the id of the service user
|
|
func (c *clientCredentialsRequest) GetSubject() string {
|
|
return c.sub
|
|
}
|
|
|
|
// GetAudience returns the audience for token to be created because of the client credentials request
|
|
func (c *clientCredentialsRequest) GetAudience() []string {
|
|
return c.audience
|
|
}
|
|
|
|
func (c *clientCredentialsRequest) GetScopes() []string {
|
|
return c.scopes
|
|
}
|
|
|
|
func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecret string) (op.Client, error) {
|
|
user, err := s.query.GetUserByLoginName(ctx, false, clientID)
|
|
if zerrors.IsNotFound(err) {
|
|
return nil, oidc.ErrInvalidClient().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("client not found")
|
|
}
|
|
if err != nil {
|
|
return nil, err // defaults to server error
|
|
}
|
|
if user.Machine == nil || user.Machine.EncodedSecret == "" {
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-pieP8", "Errors.User.Machine.Secret.NotExisting")
|
|
}
|
|
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
|
updated, err := s.hasher.Verify(user.Machine.EncodedSecret, clientSecret)
|
|
spanPasswordComparison.EndWithError(err)
|
|
if err != nil {
|
|
s.command.MachineSecretCheckFailed(ctx, user.ID, user.ResourceOwner)
|
|
return nil, zerrors.ThrowInvalidArgument(err, "OIDC-VoXo6", "Errors.User.Machine.Secret.Invalid")
|
|
}
|
|
|
|
s.command.MachineSecretCheckSucceeded(ctx, user.ID, user.ResourceOwner, updated)
|
|
return &clientCredentialsClient{
|
|
id: clientID,
|
|
user: user,
|
|
}, nil
|
|
}
|
|
|
|
type clientCredentialsClient struct {
|
|
id string
|
|
user *query.User
|
|
}
|
|
|
|
// AccessTokenType returns the AccessTokenType for the token to be created because of the client credentials request
|
|
// machine users currently only have opaque tokens ([op.AccessTokenTypeBearer])
|
|
func (c *clientCredentialsClient) AccessTokenType() op.AccessTokenType {
|
|
return accessTokenTypeToOIDC(c.user.Machine.AccessTokenType)
|
|
}
|
|
|
|
// GetID returns the client_id (username of the machine user) for the token to be created because of the client credentials request
|
|
func (c *clientCredentialsClient) GetID() string {
|
|
return c.id
|
|
}
|
|
|
|
// RedirectURIs returns nil as there are no redirect uris
|
|
func (c *clientCredentialsClient) RedirectURIs() []string {
|
|
return nil
|
|
}
|
|
|
|
// PostLogoutRedirectURIs returns nil as there are no logout redirect uris
|
|
func (c *clientCredentialsClient) PostLogoutRedirectURIs() []string {
|
|
return nil
|
|
}
|
|
|
|
// ApplicationType returns [op.ApplicationTypeWeb] as the machine users is a confidential client
|
|
func (c *clientCredentialsClient) ApplicationType() op.ApplicationType {
|
|
return op.ApplicationTypeWeb
|
|
}
|
|
|
|
// AuthMethod returns the allowed auth method type for machine user.
|
|
// It returns Basic Auth
|
|
func (c *clientCredentialsClient) AuthMethod() oidc.AuthMethod {
|
|
return oidc.AuthMethodBasic
|
|
}
|
|
|
|
// ResponseTypes returns nil as the types are only required for an authorization request
|
|
func (c *clientCredentialsClient) ResponseTypes() []oidc.ResponseType {
|
|
return nil
|
|
}
|
|
|
|
// GrantTypes returns the grant types supported by the machine users, which is currently only client credentials ([oidc.GrantTypeClientCredentials])
|
|
func (c *clientCredentialsClient) GrantTypes() []oidc.GrantType {
|
|
return []oidc.GrantType{
|
|
oidc.GrantTypeClientCredentials,
|
|
}
|
|
}
|
|
|
|
// LoginURL returns an empty string as there is no login UI involved
|
|
func (c *clientCredentialsClient) LoginURL(_ string) string {
|
|
return ""
|
|
}
|
|
|
|
// IDTokenLifetime returns 0 as there is no id_token issued
|
|
func (c *clientCredentialsClient) IDTokenLifetime() time.Duration {
|
|
return 0
|
|
}
|
|
|
|
// DevMode returns false as there is no dev mode
|
|
func (c *clientCredentialsClient) DevMode() bool {
|
|
return false
|
|
}
|
|
|
|
// RestrictAdditionalIdTokenScopes returns nil as no id_token is issued
|
|
func (c *clientCredentialsClient) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
|
|
return nil
|
|
}
|
|
|
|
// RestrictAdditionalAccessTokenScopes returns the scope allowed for the token to be created because of the client credentials request
|
|
// currently it allows all scopes to be used in the access token
|
|
func (c *clientCredentialsClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
|
|
return func(scopes []string) []string {
|
|
return scopes
|
|
}
|
|
}
|
|
|
|
func (c *clientCredentialsClient) IsScopeAllowed(scope string) bool {
|
|
return isScopeAllowed(scope) || strings.HasPrefix(scope, ScopeProjectRolePrefix)
|
|
}
|
|
|
|
// IDTokenUserinfoClaimsAssertion returns null false as no id_token is issued
|
|
func (c *clientCredentialsClient) IDTokenUserinfoClaimsAssertion() bool {
|
|
return false
|
|
}
|
|
|
|
// ClockSkew enable handling clock skew of the token validation. The duration (0-5s) will be added to exp claim and subtracted from iats,
|
|
// auth_time and nbf of the token to be created because of the client credentials request.
|
|
// It returns 0 as clock skew is not implemented on machine users.
|
|
func (c *clientCredentialsClient) ClockSkew() time.Duration {
|
|
return 0
|
|
}
|