2024-05-16 05:07:56 +00:00
|
|
|
package oidc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"slices"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/op"
|
|
|
|
|
2024-05-23 05:35:10 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
2024-05-16 05:07:56 +00:00
|
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s *Server) CodeExchange(ctx context.Context, r *op.ClientRequest[oidc.AccessTokenRequest]) (_ *op.Response, err error) {
|
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() {
|
|
|
|
span.EndWithError(err)
|
|
|
|
err = oidcError(err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
client, ok := r.Client.(*Client)
|
|
|
|
if !ok {
|
|
|
|
return nil, zerrors.ThrowInternal(nil, "OIDC-Ae2ph", "Error.Internal")
|
|
|
|
}
|
|
|
|
|
|
|
|
plainCode, err := s.decryptCode(ctx, r.Data.Code)
|
|
|
|
if err != nil {
|
|
|
|
return nil, zerrors.ThrowInvalidArgument(err, "OIDC-ahLi2", "Errors.User.Code.Invalid")
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
session *command.OIDCSession
|
|
|
|
)
|
|
|
|
if strings.HasPrefix(plainCode, command.IDPrefixV2) {
|
2024-06-12 11:53:56 +00:00
|
|
|
session, _, err = s.command.CreateOIDCSessionFromAuthRequest(
|
2024-05-16 05:07:56 +00:00
|
|
|
setContextUserSystem(ctx),
|
|
|
|
plainCode,
|
|
|
|
codeExchangeComplianceChecker(client, r.Data),
|
|
|
|
slices.Contains(client.GrantTypes(), oidc.GrantTypeRefreshToken),
|
|
|
|
)
|
|
|
|
} else {
|
2024-06-12 11:53:56 +00:00
|
|
|
session, err = s.codeExchangeV1(ctx, client, r.Data, r.Data.Code)
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-12 11:53:56 +00:00
|
|
|
return response(s.accessTokenResponseFromSession(ctx, client, session, "", client.client.ProjectID, client.client.ProjectRoleAssertion, client.client.AccessTokenRoleAssertion, client.client.IDTokenRoleAssertion, client.client.IDTokenUserinfoAssertion))
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// codeExchangeV1 creates a v2 token from a v1 auth request.
|
2024-06-12 11:53:56 +00:00
|
|
|
func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.AccessTokenRequest, code string) (session *command.OIDCSession, err error) {
|
2024-05-16 05:07:56 +00:00
|
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
|
|
|
|
authReq, err := s.getAuthRequestV1ByCode(ctx, code)
|
|
|
|
if err != nil {
|
2024-06-12 11:53:56 +00:00
|
|
|
return nil, err
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if challenge := authReq.GetCodeChallenge(); challenge != nil || client.AuthMethod() == oidc.AuthMethodNone {
|
|
|
|
if err = op.AuthorizeCodeChallenge(req.CodeVerifier, challenge); err != nil {
|
2024-06-12 11:53:56 +00:00
|
|
|
return nil, err
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if req.RedirectURI != authReq.GetRedirectURI() {
|
2024-06-12 11:53:56 +00:00
|
|
|
return nil, oidc.ErrInvalidGrant().WithDescription("redirect_uri does not correspond")
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
scope := authReq.GetScopes()
|
|
|
|
session, err = s.command.CreateOIDCSession(ctx,
|
2024-05-23 05:35:10 +00:00
|
|
|
authReq.UserID,
|
|
|
|
authReq.UserOrgID,
|
2024-05-16 05:07:56 +00:00
|
|
|
client.client.ClientID,
|
|
|
|
scope,
|
2024-05-23 05:35:10 +00:00
|
|
|
authReq.Audience,
|
|
|
|
authReq.AuthMethods(),
|
|
|
|
authReq.AuthTime,
|
2024-05-16 05:07:56 +00:00
|
|
|
authReq.GetNonce(),
|
2024-05-23 05:35:10 +00:00
|
|
|
authReq.PreferredLanguage,
|
2024-07-03 07:43:34 +00:00
|
|
|
authReq.ToUserAgent(),
|
2024-05-23 05:35:10 +00:00
|
|
|
domain.TokenReasonAuthRequest,
|
|
|
|
nil,
|
2024-05-16 05:07:56 +00:00
|
|
|
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
2024-09-03 13:19:00 +00:00
|
|
|
authReq.SessionID,
|
2024-05-16 05:07:56 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
2024-06-12 11:53:56 +00:00
|
|
|
return nil, err
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
2024-06-12 11:53:56 +00:00
|
|
|
return session, s.repo.DeleteAuthRequest(ctx, authReq.ID)
|
2024-05-16 05:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// getAuthRequestV1ByCode finds the v1 auth request by code.
|
|
|
|
// code needs to be the encrypted version of the ID,
|
|
|
|
// this is required by the underlying repo.
|
2024-05-23 05:35:10 +00:00
|
|
|
func (s *Server) getAuthRequestV1ByCode(ctx context.Context, code string) (*AuthRequest, error) {
|
2024-05-16 05:07:56 +00:00
|
|
|
authReq, err := s.repo.AuthRequestByCode(ctx, code)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return AuthRequestFromBusiness(authReq)
|
|
|
|
}
|
|
|
|
|
2024-05-23 05:35:10 +00:00
|
|
|
func (s *Server) getAuthRequestV1ByID(ctx context.Context, id string) (*AuthRequest, error) {
|
|
|
|
userAgentID, ok := middleware.UserAgentIDFromCtx(ctx)
|
|
|
|
if !ok {
|
|
|
|
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-TiTu7", "no user agent id")
|
|
|
|
}
|
|
|
|
resp, err := s.repo.AuthRequestByIDCheckLoggedIn(ctx, id, userAgentID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return AuthRequestFromBusiness(resp)
|
|
|
|
}
|
|
|
|
|
2024-05-16 05:07:56 +00:00
|
|
|
func codeExchangeComplianceChecker(client *Client, req *oidc.AccessTokenRequest) command.AuthRequestComplianceChecker {
|
|
|
|
return func(ctx context.Context, authReq *command.AuthRequestWriteModel) error {
|
|
|
|
if authReq.CodeChallenge != nil || client.AuthMethod() == oidc.AuthMethodNone {
|
|
|
|
err := op.AuthorizeCodeChallenge(req.CodeVerifier, CodeChallengeToOIDC(authReq.CodeChallenge))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if req.RedirectURI != authReq.RedirectURI {
|
|
|
|
return oidc.ErrInvalidGrant().WithDescription("redirect_uri does not correspond")
|
|
|
|
}
|
|
|
|
if err := authReq.CheckAuthenticated(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|