mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 11:04:25 +00:00
9d5cd12cd4
fix(oidc): define audience inside auth request instead off token creation When using the v1 OIDC Code flow, tokens would not carry the correct audience when returned as JWT. This applies to access tokens as JWT and ID tokens. Introspection would still show the correct audience. This happened because project audience was appended at token creation time. This stored the appended audience, used later in introspection or token refresh. However, the OIDC library still only had a view of the original auth request with the original audience. When signing JWTs it would use this outdated information. This change moves audience modifications to the auth request creation. This is was already the way it was done for v2 login and now v1 follows the same method. Co-authored-by: Livio Spring <livio.a@gmail.com>
316 lines
7.9 KiB
Go
316 lines
7.9 KiB
Go
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
"github.com/zitadel/oidc/v3/pkg/op"
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/user/model"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type AuthRequest struct {
|
|
*domain.AuthRequest
|
|
}
|
|
|
|
func (a *AuthRequest) GetID() string {
|
|
return a.ID
|
|
}
|
|
|
|
func (a *AuthRequest) GetACR() string {
|
|
// return a.
|
|
return "" //PLANNED: impl
|
|
}
|
|
|
|
func (a *AuthRequest) GetAMR() []string {
|
|
list := make([]string, 0)
|
|
if a.PasswordVerified {
|
|
list = append(list, Password, PWD)
|
|
}
|
|
if len(a.MFAsVerified) > 0 {
|
|
list = append(list, MFA)
|
|
for _, mfa := range a.MFAsVerified {
|
|
if amrMFA := AMRFromMFAType(mfa); amrMFA != "" {
|
|
list = append(list, amrMFA)
|
|
}
|
|
}
|
|
}
|
|
return list
|
|
}
|
|
|
|
func (a *AuthRequest) GetAudience() []string {
|
|
return a.Audience
|
|
}
|
|
|
|
func (a *AuthRequest) GetAuthTime() time.Time {
|
|
return a.AuthTime
|
|
}
|
|
|
|
func (a *AuthRequest) GetClientID() string {
|
|
return a.ApplicationID
|
|
}
|
|
|
|
func (a *AuthRequest) GetCodeChallenge() *oidc.CodeChallenge {
|
|
return CodeChallengeToOIDC(a.oidc().CodeChallenge)
|
|
}
|
|
|
|
func (a *AuthRequest) GetNonce() string {
|
|
return a.oidc().Nonce
|
|
}
|
|
|
|
func (a *AuthRequest) GetRedirectURI() string {
|
|
return a.CallbackURI
|
|
}
|
|
|
|
func (a *AuthRequest) GetResponseType() oidc.ResponseType {
|
|
return ResponseTypeToOIDC(a.oidc().ResponseType)
|
|
}
|
|
|
|
func (a *AuthRequest) GetResponseMode() oidc.ResponseMode {
|
|
return ""
|
|
}
|
|
|
|
func (a *AuthRequest) GetScopes() []string {
|
|
return a.oidc().Scopes
|
|
}
|
|
|
|
func (a *AuthRequest) GetState() string {
|
|
return a.TransferState
|
|
}
|
|
|
|
func (a *AuthRequest) GetSubject() string {
|
|
return a.UserID
|
|
}
|
|
|
|
func (a *AuthRequest) oidc() *domain.AuthRequestOIDC {
|
|
return a.Request.(*domain.AuthRequestOIDC)
|
|
}
|
|
|
|
func AuthRequestFromBusiness(authReq *domain.AuthRequest) (_ op.AuthRequest, err error) {
|
|
if _, ok := authReq.Request.(*domain.AuthRequestOIDC); !ok {
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "OIDC-Haz7A", "auth request is not of type oidc")
|
|
}
|
|
return &AuthRequest{authReq}, nil
|
|
}
|
|
|
|
func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, userAgentID, userID string, audience []string) *domain.AuthRequest {
|
|
return &domain.AuthRequest{
|
|
CreationDate: time.Now(),
|
|
AgentID: userAgentID,
|
|
BrowserInfo: ParseBrowserInfoFromContext(ctx),
|
|
ApplicationID: authReq.ClientID,
|
|
CallbackURI: authReq.RedirectURI,
|
|
TransferState: authReq.State,
|
|
Prompt: PromptToBusiness(authReq.Prompt),
|
|
PossibleLOAs: ACRValuesToBusiness(authReq.ACRValues),
|
|
UiLocales: UILocalesToBusiness(authReq.UILocales),
|
|
LoginHint: authReq.LoginHint,
|
|
SelectedIDPConfigID: GetSelectedIDPIDFromScopes(authReq.Scopes),
|
|
MaxAuthAge: MaxAgeToBusiness(authReq.MaxAge),
|
|
UserID: userID,
|
|
InstanceID: authz.GetInstance(ctx).InstanceID(),
|
|
Audience: audience,
|
|
Request: &domain.AuthRequestOIDC{
|
|
Scopes: authReq.Scopes,
|
|
ResponseType: ResponseTypeToBusiness(authReq.ResponseType),
|
|
Nonce: authReq.Nonce,
|
|
CodeChallenge: CodeChallengeToBusiness(authReq.CodeChallenge, authReq.CodeChallengeMethod),
|
|
},
|
|
}
|
|
}
|
|
|
|
func ParseBrowserInfoFromContext(ctx context.Context) *domain.BrowserInfo {
|
|
userAgent, acceptLang := HttpHeadersFromContext(ctx)
|
|
ip := IpFromContext(ctx)
|
|
return &domain.BrowserInfo{RemoteIP: ip, UserAgent: userAgent, AcceptLanguage: acceptLang}
|
|
}
|
|
|
|
func HttpHeadersFromContext(ctx context.Context) (userAgent, acceptLang string) {
|
|
ctxHeaders, ok := http_utils.HeadersFromCtx(ctx)
|
|
if !ok {
|
|
return
|
|
}
|
|
if agents, ok := ctxHeaders[http_utils.UserAgentHeader]; ok {
|
|
userAgent = agents[0]
|
|
}
|
|
if langs, ok := ctxHeaders[http_utils.AcceptLanguage]; ok {
|
|
acceptLang = langs[0]
|
|
}
|
|
return userAgent, acceptLang
|
|
}
|
|
|
|
func IpFromContext(ctx context.Context) net.IP {
|
|
ipString := http_utils.RemoteIPFromCtx(ctx)
|
|
if ipString == "" {
|
|
return nil
|
|
}
|
|
return net.ParseIP(ipString)
|
|
}
|
|
|
|
func PromptToBusiness(oidcPrompt []string) []domain.Prompt {
|
|
prompts := make([]domain.Prompt, len(oidcPrompt))
|
|
for _, oidcPrompt := range oidcPrompt {
|
|
switch oidcPrompt {
|
|
case oidc.PromptNone:
|
|
prompts = append(prompts, domain.PromptNone)
|
|
case oidc.PromptLogin:
|
|
prompts = append(prompts, domain.PromptLogin)
|
|
case oidc.PromptConsent:
|
|
prompts = append(prompts, domain.PromptConsent)
|
|
case oidc.PromptSelectAccount:
|
|
prompts = append(prompts, domain.PromptSelectAccount)
|
|
case "create": //this prompt is not final yet, so not implemented in oidc lib
|
|
prompts = append(prompts, domain.PromptCreate)
|
|
}
|
|
}
|
|
return prompts
|
|
}
|
|
|
|
func ACRValuesToBusiness(values []string) []domain.LevelOfAssurance {
|
|
return nil
|
|
}
|
|
|
|
func UILocalesToBusiness(tags []language.Tag) []string {
|
|
if tags == nil {
|
|
return nil
|
|
}
|
|
locales := make([]string, len(tags))
|
|
for i, tag := range tags {
|
|
locales[i] = tag.String()
|
|
}
|
|
return locales
|
|
}
|
|
|
|
func GetSelectedIDPIDFromScopes(scopes oidc.SpaceDelimitedArray) string {
|
|
for _, scope := range scopes {
|
|
if strings.HasPrefix(scope, domain.SelectIDPScope) {
|
|
return strings.TrimPrefix(scope, domain.SelectIDPScope)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func MaxAgeToBusiness(maxAge *uint) *time.Duration {
|
|
if maxAge == nil {
|
|
return nil
|
|
}
|
|
dur := time.Duration(*maxAge) * time.Second
|
|
return &dur
|
|
}
|
|
|
|
func ResponseTypeToBusiness(responseType oidc.ResponseType) domain.OIDCResponseType {
|
|
switch responseType {
|
|
case oidc.ResponseTypeCode:
|
|
return domain.OIDCResponseTypeCode
|
|
case oidc.ResponseTypeIDTokenOnly:
|
|
return domain.OIDCResponseTypeIDToken
|
|
case oidc.ResponseTypeIDToken:
|
|
return domain.OIDCResponseTypeIDTokenToken
|
|
default:
|
|
return domain.OIDCResponseTypeCode
|
|
}
|
|
}
|
|
|
|
func ResponseTypeToOIDC(responseType domain.OIDCResponseType) oidc.ResponseType {
|
|
switch responseType {
|
|
case domain.OIDCResponseTypeCode:
|
|
return oidc.ResponseTypeCode
|
|
case domain.OIDCResponseTypeIDTokenToken:
|
|
return oidc.ResponseTypeIDToken
|
|
case domain.OIDCResponseTypeIDToken:
|
|
return oidc.ResponseTypeIDTokenOnly
|
|
default:
|
|
return oidc.ResponseTypeCode
|
|
}
|
|
}
|
|
|
|
func CodeChallengeToBusiness(challenge string, method oidc.CodeChallengeMethod) *domain.OIDCCodeChallenge {
|
|
if challenge == "" {
|
|
return nil
|
|
}
|
|
challengeMethod := domain.CodeChallengeMethodPlain
|
|
if method == oidc.CodeChallengeMethodS256 {
|
|
challengeMethod = domain.CodeChallengeMethodS256
|
|
}
|
|
return &domain.OIDCCodeChallenge{
|
|
Challenge: challenge,
|
|
Method: challengeMethod,
|
|
}
|
|
}
|
|
|
|
func CodeChallengeToOIDC(challenge *domain.OIDCCodeChallenge) *oidc.CodeChallenge {
|
|
if challenge == nil {
|
|
return nil
|
|
}
|
|
challengeMethod := oidc.CodeChallengeMethodPlain
|
|
if challenge.Method == domain.CodeChallengeMethodS256 {
|
|
challengeMethod = oidc.CodeChallengeMethodS256
|
|
}
|
|
return &oidc.CodeChallenge{
|
|
Challenge: challenge.Challenge,
|
|
Method: challengeMethod,
|
|
}
|
|
}
|
|
|
|
func AMRFromMFAType(mfaType domain.MFAType) string {
|
|
switch mfaType {
|
|
case domain.MFATypeTOTP,
|
|
domain.MFATypeOTPSMS,
|
|
domain.MFATypeOTPEmail:
|
|
return OTP
|
|
case domain.MFATypeU2F,
|
|
domain.MFATypeU2FUserVerification:
|
|
return UserPresence
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func RefreshTokenRequestFromBusiness(tokenView *model.RefreshTokenView) op.RefreshTokenRequest {
|
|
return &RefreshTokenRequest{tokenView}
|
|
}
|
|
|
|
type RefreshTokenRequest struct {
|
|
*model.RefreshTokenView
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetAMR() []string {
|
|
return r.AuthMethodsReferences
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetAudience() []string {
|
|
return r.Audience
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetAuthTime() time.Time {
|
|
return r.AuthTime
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetClientID() string {
|
|
return r.ClientID
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetScopes() []string {
|
|
return r.Scopes
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetSubject() string {
|
|
return r.UserID
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) SetCurrentScopes(scopes []string) {
|
|
r.Scopes = scopes
|
|
}
|
|
|
|
func (r *RefreshTokenRequest) GetActor() *oidc.ActorClaims {
|
|
return actorDomainToClaims(r.Actor)
|
|
}
|