fix: handle reauth correctly (max_age=0 or prompt=login) (#1870)

* max age

* merge main

* fix when no prompt is set

* fix: update oidc pkg

* fix tests
This commit is contained in:
Livio Amstutz
2021-06-16 10:02:15 +02:00
committed by GitHub
parent 1e1ded440c
commit 4a2ca5a1e8
7 changed files with 86 additions and 48 deletions

View File

@@ -113,6 +113,7 @@ func AuthRequestFromBusiness(authReq *domain.AuthRequest) (_ op.AuthRequest, err
func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest, userAgentID, userID string) *domain.AuthRequest {
return &domain.AuthRequest{
CreationDate: time.Now(),
AgentID: userAgentID,
BrowserInfo: ParseBrowserInfoFromContext(ctx),
ApplicationID: authReq.ClientID,
@@ -122,7 +123,7 @@ func CreateAuthRequestToBusiness(ctx context.Context, authReq *oidc.AuthRequest,
PossibleLOAs: ACRValuesToBusiness(authReq.ACRValues),
UiLocales: UILocalesToBusiness(authReq.UILocales),
LoginHint: authReq.LoginHint,
MaxAuthAge: authReq.MaxAge,
MaxAuthAge: MaxAgeToBusiness(authReq.MaxAge),
UserID: userID,
Request: &domain.AuthRequestOIDC{
Scopes: authReq.Scopes,
@@ -161,21 +162,23 @@ func IpFromContext(ctx context.Context) net.IP {
return net.ParseIP(ipString)
}
func PromptToBusiness(prompt oidc.Prompt) domain.Prompt {
switch prompt {
case oidc.PromptNone:
return domain.PromptNone
case oidc.PromptLogin:
return domain.PromptLogin
case oidc.PromptConsent:
return domain.PromptConsent
case oidc.PromptSelectAccount:
return domain.PromptSelectAccount
case "create": //this prompt is not final yet, so not implemented in oidc lib
return domain.PromptCreate
default:
return domain.PromptUnspecified
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 {
@@ -193,6 +196,14 @@ func UILocalesToBusiness(tags []language.Tag) []string {
return locales
}
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:
@@ -291,6 +302,6 @@ func (r *RefreshTokenRequest) GetSubject() string {
return r.UserID
}
func (r *RefreshTokenRequest) SetCurrentScopes(scopes oidc.Scopes) {
func (r *RefreshTokenRequest) SetCurrentScopes(scopes []string) {
r.Scopes = scopes
}

View File

@@ -80,7 +80,7 @@ func (o *OPStorage) GetKeyByIDAndIssuer(ctx context.Context, keyID, issuer strin
}, nil
}
func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string, scopes oidc.Scopes) (oidc.Scopes, error) {
func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string, scopes []string) ([]string, error) {
user, err := o.repo.UserByID(ctx, subject)
if err != nil {
return nil, err

View File

@@ -523,7 +523,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "Errors.Internal")
}
steps := make([]domain.NextStep, 0)
if !checkLoggedIn && request.Prompt == domain.PromptNone {
if !checkLoggedIn && domain.IsPrompt(request.Prompt, domain.PromptNone) {
return append(steps, &domain.RedirectToCallbackStep{}), nil
}
if request.UserID == "" {
@@ -532,15 +532,15 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
return steps, nil
}
steps = append(steps, new(domain.LoginStep))
if request.Prompt == domain.PromptCreate {
if domain.IsPrompt(request.Prompt, domain.PromptCreate) {
return append(steps, &domain.RegistrationStep{}), nil
}
if request.Prompt == domain.PromptSelectAccount || request.Prompt == domain.PromptUnspecified {
if len(request.Prompt) == 0 || domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) {
users, err := repo.usersForUserSelection(request)
if err != nil {
return nil, err
}
if len(users) > 0 || request.Prompt == domain.PromptSelectAccount {
if len(users) > 0 || domain.IsPrompt(request.Prompt, domain.PromptSelectAccount) {
steps = append(steps, &domain.SelectUserStep{Users: users})
}
}
@@ -557,7 +557,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
}
isInternalLogin := request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == ""
if !isInternalLogin && len(request.LinkingUsers) == 0 && !checkVerificationTime(userSession.ExternalLoginVerification, repo.ExternalLoginCheckLifeTime) {
if !isInternalLogin && len(request.LinkingUsers) == 0 && !checkVerificationTimeMaxAge(userSession.ExternalLoginVerification, repo.ExternalLoginCheckLifeTime, request) {
selectedIDPConfigID := request.SelectedIDPConfigID
if selectedIDPConfigID == "" {
selectedIDPConfigID = userSession.SelectedIDPConfigID
@@ -638,7 +638,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use
var step domain.NextStep
if request.LoginPolicy.PasswordlessType != domain.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() {
if checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) {
if checkVerificationTimeMaxAge(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime, request) {
request.AuthTime = userSession.PasswordlessVerification
return nil
}
@@ -649,7 +649,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *domain.AuthRequest, use
return &domain.InitPasswordStep{}
}
if checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
if checkVerificationTimeMaxAge(userSession.PasswordVerification, repo.PasswordCheckLifeTime, request) {
request.PasswordVerified = true
request.AuthTime = userSession.PasswordVerification
return nil
@@ -686,14 +686,14 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
}
fallthrough
case domain.MFALevelSecondFactor:
if checkVerificationTime(userSession.SecondFactorVerification, repo.SecondFactorCheckLifeTime) {
if checkVerificationTimeMaxAge(userSession.SecondFactorVerification, repo.SecondFactorCheckLifeTime, request) {
request.MFAsVerified = append(request.MFAsVerified, auth_req_model.MFATypeToDomain(userSession.SecondFactorVerificationType))
request.AuthTime = userSession.SecondFactorVerification
return nil, true, nil
}
fallthrough
case domain.MFALevelMultiFactor:
if checkVerificationTime(userSession.MultiFactorVerification, repo.MultiFactorCheckLifeTime) {
if checkVerificationTimeMaxAge(userSession.MultiFactorVerification, repo.MultiFactorCheckLifeTime, request) {
request.MFAsVerified = append(request.MFAsVerified, auth_req_model.MFATypeToDomain(userSession.MultiFactorVerificationType))
request.AuthTime = userSession.MultiFactorVerification
return nil, true, nil
@@ -765,6 +765,16 @@ func getLoginPolicyIDPProviders(provider idpProviderViewProvider, iamID, orgID s
return iam_es_model.IDPProviderViewsToModel(idpProviders), nil
}
func checkVerificationTimeMaxAge(verificationTime time.Time, lifetime time.Duration, request *domain.AuthRequest) bool {
if !checkVerificationTime(verificationTime, lifetime) {
return false
}
if request.MaxAuthAge == nil {
return true
}
return verificationTime.After(request.CreationDate.Add(-*request.MaxAuthAge))
}
func checkVerificationTime(verificationTime time.Time, lifetime time.Duration) bool {
return verificationTime.Add(lifetime).After(time.Now().UTC())
}

View File

@@ -251,7 +251,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
{
"prompt none and checkLoggedIn false, callback step",
fields{},
args{&domain.AuthRequest{Prompt: domain.PromptNone}, false},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptNone}}, false},
[]domain.NextStep{&domain.RedirectToCallbackStep{}},
nil,
},
@@ -278,7 +278,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
fields{
userSessionViewProvider: &mockViewErrUserSession{},
},
args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount}, false},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}}, false},
nil,
errors.IsInternal,
},
@@ -301,7 +301,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
},
args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount}, false},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}}, false},
[]domain.NextStep{
&domain.LoginStep{},
&domain.SelectUserStep{
@@ -341,7 +341,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
},
args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount, RequestedOrgID: "orgID1"}, false},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}, RequestedOrgID: "orgID1"}, false},
[]domain.NextStep{
&domain.LoginStep{},
&domain.SelectUserStep{
@@ -370,7 +370,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
},
args{&domain.AuthRequest{Prompt: domain.PromptSelectAccount}, false},
args{&domain.AuthRequest{Prompt: []domain.Prompt{domain.PromptSelectAccount}}, false},
[]domain.NextStep{
&domain.LoginStep{},
&domain.SelectUserStep{
@@ -848,7 +848,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
args{&domain.AuthRequest{
UserID: "UserID",
Prompt: domain.PromptNone,
Prompt: []domain.Prompt{domain.PromptNone},
Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
@@ -880,7 +880,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
args{&domain.AuthRequest{
UserID: "UserID",
Prompt: domain.PromptNone,
Prompt: []domain.Prompt{domain.PromptNone},
Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
@@ -912,7 +912,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
args{&domain.AuthRequest{
UserID: "UserID",
Prompt: domain.PromptNone,
Prompt: []domain.Prompt{domain.PromptNone},
Request: &domain.AuthRequestOIDC{},
LoginPolicy: &domain.LoginPolicy{
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},

View File

@@ -18,11 +18,11 @@ type AuthRequest struct {
ApplicationID string
CallbackURI string
TransferState string
Prompt Prompt
Prompt []Prompt
PossibleLOAs []LevelOfAssurance
UiLocales []string
LoginHint string
MaxAuthAge uint32
MaxAuthAge *time.Duration
Request Request
levelOfAssurance LevelOfAssurance
@@ -75,6 +75,15 @@ const (
PromptCreate
)
func IsPrompt(prompt []Prompt, requestedPrompt Prompt) bool {
for _, p := range prompt {
if p == requestedPrompt {
return true
}
}
return false
}
type LevelOfAssurance int
const (