feat(oidc): implement amr claim (#2969)
This adds the amr claim which stores methods used to authenticate with Authelia by the users session.pull/2789/head^2
parent
b2d35d88ec
commit
0116506330
|
@ -422,8 +422,8 @@ _**Important Note:** The claim `sub` is planned to be changed in the future to a
|
|||
individual user. Please use the claim `preferred_username` instead._
|
||||
|
||||
| Claim | JWT Type | Authelia Attribute | Description |
|
||||
|:------------------:|:-------------:|:------------------:|:---------------------------------------------:|
|
||||
| sub | string | username | The username the user used to login with |
|
||||
|:---------:|:-------------:|:------------------:|:-----------------------------------------------------------:|
|
||||
| sub | string | username | A unique value linked to the user who logged in |
|
||||
| scope | string | scopes | Granted scopes (space delimited) |
|
||||
| scp | array[string] | scopes | Granted scopes |
|
||||
| iss | string | hostname | The issuer name, determined by URL |
|
||||
|
@ -434,6 +434,7 @@ individual user. Please use the claim `preferred_username` instead._
|
|||
| rat | number | _N/A_ | The time when the token was requested |
|
||||
| iat | number | _N/A_ | The time when the token was issued |
|
||||
| jti | string(uuid) | _N/A_ | JWT Identifier |
|
||||
| amr | array[string] | _N/A_ | An [RFC8176] list of authentication method reference values |
|
||||
|
||||
### groups
|
||||
|
||||
|
@ -462,6 +463,28 @@ This scope includes the profile information the authentication backend reports a
|
|||
| preferred_username | string | username | The username the user used to login with |
|
||||
| name | string | display_name | The users display name |
|
||||
|
||||
## Authentication Method References
|
||||
|
||||
Authelia currently supports adding the `amr` claim to the [ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)
|
||||
utilizing the [RFC8176] Authentication Method Reference values.
|
||||
|
||||
The values this claim has are not strictly defined by the [OpenID Connect] specification. As such, some backends may
|
||||
expect a specification other than [RFC8176] for this purpose. If you have such an application and wish for us to support
|
||||
it then you're encouraged to create an issue.
|
||||
|
||||
Below is a list of the potential values we place in the claim and their meaning:
|
||||
|
||||
| Value | Description | Factor | Channel |
|
||||
|:-----:|:----------------------------------------------------------------:|:------:|:--------:|
|
||||
| mfa | User used multiple factors to login (see factor column) | N/A | N/A |
|
||||
| mca | User used multiple channels to login (see channel column) | N/A | N/A |
|
||||
| user | User confirmed they were present when using their hardware key | N/A | N/A |
|
||||
| pin | User confirmed they are the owner of the hardware key with a pin | N/A | N/A |
|
||||
| pwd | User used a username and password to login | Know | Browser |
|
||||
| otp | User used TOTP to login | Have | Browser |
|
||||
| hwk | User used a hardware key to login | Have | Browser |
|
||||
| sms | User used Duo to login | Have | External |
|
||||
|
||||
## Endpoint Implementations
|
||||
|
||||
This is a table of the endpoints we currently support and their paths. This can be requrired information for some RP's,
|
||||
|
@ -482,3 +505,4 @@ Authelia via https://auth.example.com, the discovery URL is https://auth.example
|
|||
|
||||
[OpenID Connect]: https://openid.net/connect/
|
||||
[token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration
|
||||
[RFC8176]: https://datatracker.ietf.org/doc/html/rfc8176
|
|
@ -9,7 +9,9 @@ import (
|
|||
|
||||
"github.com/ory/fosite"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
)
|
||||
|
@ -97,7 +99,7 @@ func oidcAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *
|
|||
|
||||
subject := userSession.Username
|
||||
oidcSession := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
|
||||
subject, userSession.Username, extraClaims, authTime, workflowCreated, requester)
|
||||
subject, userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, workflowCreated, requester)
|
||||
|
||||
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",
|
||||
requester.GetID(), oidcSession.ClientID, oidcSession.Subject, oidcSession.Username, oidcSession.Claims)
|
||||
|
@ -126,13 +128,13 @@ func oidcAuthorizeHandleAuthorizationOrConsentInsufficient(
|
|||
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' requires user '%s' provides consent for scopes '%s'",
|
||||
requester.GetID(), client.GetID(), userSession.Username, strings.Join(requester.GetRequestedScopes(), "', '"))
|
||||
|
||||
userSession.OIDCWorkflowSession = &session.OIDCWorkflowSession{
|
||||
userSession.OIDCWorkflowSession = &model.OIDCWorkflowSession{
|
||||
ClientID: client.GetID(),
|
||||
RequestedScopes: requester.GetRequestedScopes(),
|
||||
RequestedAudience: requester.GetRequestedAudience(),
|
||||
AuthURI: redirectURL,
|
||||
TargetURI: requester.GetRedirectURI().String(),
|
||||
RequiredAuthorizationLevel: client.Policy,
|
||||
Require2FA: client.Policy == authorization.TwoFactor,
|
||||
CreatedTimestamp: time.Now().Unix(),
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ func oidcConsent(ctx *middlewares.AutheliaCtx) {
|
|||
}
|
||||
|
||||
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
|
||||
ctx.Logger.Debugf("Insufficient permissions to give consent v2 %d -> %d", userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.RequiredAuthorizationLevel)
|
||||
ctx.Logger.Debugf("Insufficient permissions to give consent during GET current level: %d, require 2FA: %t", userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.Require2FA)
|
||||
ctx.ReplyForbidden()
|
||||
|
||||
return
|
||||
|
@ -59,7 +59,7 @@ func oidcConsentPOST(ctx *middlewares.AutheliaCtx) {
|
|||
}
|
||||
|
||||
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
|
||||
ctx.Logger.Debugf("Insufficient permissions to give consent v1 %d -> %d", userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.RequiredAuthorizationLevel)
|
||||
ctx.Logger.Debugf("Insufficient permissions to give consent during POST current level: %d, require 2FA: %t", userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.Require2FA)
|
||||
ctx.ReplyForbidden()
|
||||
|
||||
return
|
||||
|
|
|
@ -255,7 +255,7 @@ func HandleAllow(ctx *middlewares.AutheliaCtx, targetURL string) {
|
|||
return
|
||||
}
|
||||
|
||||
userSession.SetTwoFactor(ctx.Clock.Now())
|
||||
userSession.SetTwoFactorDuo(ctx.Clock.Now())
|
||||
|
||||
err = ctx.SaveSession(userSession)
|
||||
if err != nil {
|
||||
|
|
|
@ -68,7 +68,7 @@ func SecondFactorTOTPPost(ctx *middlewares.AutheliaCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
userSession.SetTwoFactor(ctx.Clock.Now())
|
||||
userSession.SetTwoFactorTOTP(ctx.Clock.Now())
|
||||
|
||||
if err = ctx.SaveSession(userSession); err != nil {
|
||||
ctx.Logger.Errorf(logFmtErrSessionSave, "authentication time", regulation.AuthTypeTOTP, userSession.Username, err)
|
||||
|
|
|
@ -185,8 +185,9 @@ func SecondFactorWebauthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
|||
return
|
||||
}
|
||||
|
||||
userSession.SetTwoFactor(ctx.Clock.Now())
|
||||
userSession.Webauthn = nil
|
||||
userSession.SetTwoFactorWebauthn(ctx.Clock.Now(),
|
||||
assertionResponse.Response.AuthenticatorData.Flags.UserPresent(),
|
||||
assertionResponse.Response.AuthenticatorData.Flags.UserVerified())
|
||||
|
||||
if err = ctx.SaveSession(userSession); err != nil {
|
||||
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the assertion challenge and authentication time", regulation.AuthTypeWebauthn, userSession.Username, err)
|
||||
|
|
|
@ -3,6 +3,7 @@ package handlers
|
|||
import (
|
||||
"github.com/ory/fosite"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
|
@ -10,7 +11,7 @@ import (
|
|||
|
||||
// isConsentMissing compares the requestedScopes and requestedAudience to the workflows
|
||||
// GrantedScopes and GrantedAudience and returns true if they do not match or the workflow is nil.
|
||||
func isConsentMissing(workflow *session.OIDCWorkflowSession, requestedScopes, requestedAudience []string) (isMissing bool) {
|
||||
func isConsentMissing(workflow *model.OIDCWorkflowSession, requestedScopes, requestedAudience []string) (isMissing bool) {
|
||||
if workflow == nil {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -6,19 +6,20 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
)
|
||||
|
||||
func TestShouldDetectIfConsentIsMissing(t *testing.T) {
|
||||
var workflow *session.OIDCWorkflowSession
|
||||
var workflow *model.OIDCWorkflowSession
|
||||
|
||||
requestedScopes := []string{"openid", "profile"}
|
||||
requestedAudience := []string{"https://authelia.com"}
|
||||
|
||||
assert.True(t, isConsentMissing(workflow, requestedScopes, requestedAudience))
|
||||
|
||||
workflow = &session.OIDCWorkflowSession{
|
||||
workflow = &model.OIDCWorkflowSession{
|
||||
GrantedScopes: []string{"openid", "profile"},
|
||||
GrantedAudience: []string{"https://authelia.com"},
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
|
@ -16,7 +17,7 @@ import (
|
|||
func handleOIDCWorkflowResponse(ctx *middlewares.AutheliaCtx) {
|
||||
userSession := ctx.GetSession()
|
||||
|
||||
if !authorization.IsAuthLevelSufficient(userSession.AuthenticationLevel, userSession.OIDCWorkflowSession.RequiredAuthorizationLevel) {
|
||||
if userSession.OIDCWorkflowSession.Require2FA && userSession.AuthenticationLevel != authentication.TwoFactor {
|
||||
ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", userSession.OIDCWorkflowSession.ClientID)
|
||||
ctx.ReplyOK()
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package model
|
||||
|
||||
// OIDCWorkflowSession represent an OIDC workflow session.
|
||||
type OIDCWorkflowSession struct {
|
||||
ClientID string
|
||||
RequestedScopes []string
|
||||
GrantedScopes []string
|
||||
RequestedAudience []string
|
||||
GrantedAudience []string
|
||||
TargetURI string
|
||||
AuthURI string
|
||||
Require2FA bool
|
||||
CreatedTimestamp int64
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package oidc
|
||||
|
||||
// AuthenticationMethodsReferences holds AMR information.
|
||||
type AuthenticationMethodsReferences struct {
|
||||
UsernameAndPassword bool
|
||||
TOTP bool
|
||||
Duo bool
|
||||
Webauthn bool
|
||||
WebauthnUserPresence bool
|
||||
WebauthnUserVerified bool
|
||||
}
|
||||
|
||||
// FactorKnowledge returns true if a "something you know" factor of authentication was used.
|
||||
func (r AuthenticationMethodsReferences) FactorKnowledge() bool {
|
||||
return r.UsernameAndPassword
|
||||
}
|
||||
|
||||
// FactorPossession returns true if a "something you have" factor of authentication was used.
|
||||
func (r AuthenticationMethodsReferences) FactorPossession() bool {
|
||||
return r.TOTP || r.Webauthn || r.Duo
|
||||
}
|
||||
|
||||
// MultiFactorAuthentication returns true if multiple factors were used.
|
||||
func (r AuthenticationMethodsReferences) MultiFactorAuthentication() bool {
|
||||
return r.FactorKnowledge() && r.FactorPossession()
|
||||
}
|
||||
|
||||
// ChannelBrowser returns true if a browser was used to authenticate.
|
||||
func (r AuthenticationMethodsReferences) ChannelBrowser() bool {
|
||||
return r.UsernameAndPassword || r.TOTP || r.Webauthn
|
||||
}
|
||||
|
||||
// ChannelService returns true if a non-browser service was used to authenticate.
|
||||
func (r AuthenticationMethodsReferences) ChannelService() bool {
|
||||
return r.Duo
|
||||
}
|
||||
|
||||
// MultiChannelAuthentication returns true if the user used more than one channel to authenticate.
|
||||
func (r AuthenticationMethodsReferences) MultiChannelAuthentication() bool {
|
||||
return r.ChannelBrowser() && r.ChannelService()
|
||||
}
|
||||
|
||||
// MarshalRFC8176 returns the AMR claim slice of strings in the RFC8176 format.
|
||||
// https://datatracker.ietf.org/doc/html/rfc8176
|
||||
func (r AuthenticationMethodsReferences) MarshalRFC8176() []string {
|
||||
var amr []string
|
||||
|
||||
if r.UsernameAndPassword {
|
||||
amr = append(amr, AMRPasswordBasedAuthentication)
|
||||
}
|
||||
|
||||
if r.TOTP {
|
||||
amr = append(amr, AMROneTimePassword)
|
||||
}
|
||||
|
||||
if r.Duo {
|
||||
amr = append(amr, AMRShortMessageService)
|
||||
}
|
||||
|
||||
if r.Webauthn {
|
||||
amr = append(amr, AMRHardwareSecuredKey)
|
||||
}
|
||||
|
||||
if r.WebauthnUserPresence {
|
||||
amr = append(amr, AMRUserPresence)
|
||||
}
|
||||
|
||||
if r.WebauthnUserVerified {
|
||||
amr = append(amr, AMRPersonalIdentificationNumber)
|
||||
}
|
||||
|
||||
if r.MultiFactorAuthentication() {
|
||||
amr = append(amr, AMRMultiFactorAuthentication)
|
||||
}
|
||||
|
||||
if r.MultiChannelAuthentication() {
|
||||
amr = append(amr, AMRMultiChannelAuthentication)
|
||||
}
|
||||
|
||||
return amr
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testAMRWant struct {
|
||||
FactorKnowledge, FactorPossession, MultiFactorAuthentication bool
|
||||
ChannelBrowser, ChannelService, MultiChannelAuthentication bool
|
||||
|
||||
RFC8176 []string
|
||||
}
|
||||
|
||||
func TestAuthenticationMethodsReferences(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
is AuthenticationMethodsReferences
|
||||
want testAMRWant
|
||||
}{
|
||||
{
|
||||
desc: "Username and Password",
|
||||
|
||||
is: AuthenticationMethodsReferences{UsernameAndPassword: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: true,
|
||||
FactorPossession: false,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: false,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"pwd"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TOTP",
|
||||
|
||||
is: AuthenticationMethodsReferences{TOTP: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: false,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"otp"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Webauthn",
|
||||
|
||||
is: AuthenticationMethodsReferences{Webauthn: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: false,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"hwk"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Webauthn User Presence",
|
||||
|
||||
is: AuthenticationMethodsReferences{WebauthnUserPresence: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: false,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: false,
|
||||
ChannelService: false,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"user"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Webauthn User Verified",
|
||||
|
||||
is: AuthenticationMethodsReferences{WebauthnUserVerified: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: false,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: false,
|
||||
ChannelService: false,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"pin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Webauthn with User Presence and Verified",
|
||||
|
||||
is: AuthenticationMethodsReferences{Webauthn: true, WebauthnUserVerified: true, WebauthnUserPresence: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: false,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"hwk", "user", "pin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Duo",
|
||||
|
||||
is: AuthenticationMethodsReferences{Duo: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: false,
|
||||
ChannelService: true,
|
||||
MultiChannelAuthentication: false,
|
||||
RFC8176: []string{"sms"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Duo Webauthn TOTP",
|
||||
|
||||
is: AuthenticationMethodsReferences{Duo: true, Webauthn: true, TOTP: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: true,
|
||||
MultiChannelAuthentication: true,
|
||||
RFC8176: []string{"sms", "hwk", "otp", "mca"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Duo TOTP",
|
||||
|
||||
is: AuthenticationMethodsReferences{Duo: true, TOTP: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: false,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: false,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: true,
|
||||
MultiChannelAuthentication: true,
|
||||
RFC8176: []string{"sms", "otp", "mca"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Username and Password with Duo",
|
||||
|
||||
is: AuthenticationMethodsReferences{Duo: true, UsernameAndPassword: true},
|
||||
want: testAMRWant{
|
||||
FactorKnowledge: true,
|
||||
FactorPossession: true,
|
||||
MultiFactorAuthentication: true,
|
||||
ChannelBrowser: true,
|
||||
ChannelService: true,
|
||||
MultiChannelAuthentication: true,
|
||||
RFC8176: []string{"pwd", "sms", "mfa", "mca"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want.FactorKnowledge, tc.is.FactorKnowledge())
|
||||
assert.Equal(t, tc.want.FactorPossession, tc.is.FactorPossession())
|
||||
assert.Equal(t, tc.want.MultiFactorAuthentication, tc.is.MultiFactorAuthentication())
|
||||
assert.Equal(t, tc.want.ChannelBrowser, tc.is.ChannelBrowser())
|
||||
assert.Equal(t, tc.want.ChannelService, tc.is.ChannelService())
|
||||
assert.Equal(t, tc.want.MultiChannelAuthentication, tc.is.MultiChannelAuthentication())
|
||||
|
||||
isRFC8176 := tc.is.MarshalRFC8176()
|
||||
|
||||
for _, amr := range tc.want.RFC8176 {
|
||||
t.Run(fmt.Sprintf("has all wanted/%s", amr), func(t *testing.T) {
|
||||
assert.Contains(t, isRFC8176, amr)
|
||||
})
|
||||
}
|
||||
|
||||
for _, amr := range isRFC8176 {
|
||||
t.Run(fmt.Sprintf("only has wanted/%s", amr), func(t *testing.T) {
|
||||
assert.Contains(t, tc.want.RFC8176, amr)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
)
|
||||
|
||||
// NewClient creates a new InternalClient.
|
||||
|
@ -46,8 +46,8 @@ func (c InternalClient) GetID() string {
|
|||
return c.ID
|
||||
}
|
||||
|
||||
// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
|
||||
func (c InternalClient) GetConsentResponseBody(session *session.OIDCWorkflowSession) ConsentGetResponseBody {
|
||||
// GetConsentResponseBody returns the proper consent response body for this model.OIDCWorkflowSession.
|
||||
func (c InternalClient) GetConsentResponseBody(session *model.OIDCWorkflowSession) ConsentGetResponseBody {
|
||||
body := ConsentGetResponseBody{
|
||||
ClientID: c.ID,
|
||||
ClientDescription: c.Description,
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/session"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
|
@ -79,7 +79,7 @@ func TestInternalClient_GetConsentResponseBody(t *testing.T) {
|
|||
c.ID = "myclient"
|
||||
c.Description = "My Client"
|
||||
|
||||
workflow := &session.OIDCWorkflowSession{
|
||||
workflow := &model.OIDCWorkflowSession{
|
||||
RequestedAudience: []string{"https://example.com"},
|
||||
RequestedScopes: []string{"openid", "groups"},
|
||||
}
|
||||
|
|
|
@ -31,3 +31,97 @@ const (
|
|||
RevocationPath = "/api/oidc/revocation"
|
||||
UserinfoPath = "/api/oidc/userinfo"
|
||||
)
|
||||
|
||||
// Authentication Method Reference Values https://datatracker.ietf.org/doc/html/rfc8176
|
||||
const (
|
||||
// AMRMultiFactorAuthentication is an RFC8176 Authentication Method Reference Value that represents multiple-factor
|
||||
// authentication as per NIST.800-63-2 and ISO29115. When this is present, specific authentication methods used may
|
||||
// also be included.
|
||||
//
|
||||
// Authelia utilizes this when a user has performed any 2 AMR's with different factor values (excluding meta).
|
||||
// Factor: Meta, Channel: Meta.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
//
|
||||
// NIST.800-63-2: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63-2.pdf
|
||||
//
|
||||
// ISO29115: https://www.iso.org/standard/45138.html
|
||||
AMRMultiFactorAuthentication = "mfa"
|
||||
|
||||
// AMRMultiChannelAuthentication is an RFC8176 Authentication Method Reference Value that represents
|
||||
// multiple-channel authentication. The authentication involves communication over more than one distinct
|
||||
// communication channel. For instance, a multiple-channel authentication might involve both entering information
|
||||
// into a workstation's browser and providing information on a telephone call to a pre-registered number.
|
||||
//
|
||||
// Authelia utilizes this when a user has performed any 2 AMR's with different channel values (excluding meta).
|
||||
// Factor: Meta, Channel: Meta.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
AMRMultiChannelAuthentication = "mca"
|
||||
|
||||
// AMRUserPresence is an RFC8176 Authentication Method Reference Value that represents authentication that included
|
||||
// a user presence test. Evidence that the end user is present and interacting with the device. This is sometimes
|
||||
// also referred to as "test of user presence" as per W3C.WD-webauthn-20170216.
|
||||
//
|
||||
// Authelia utilizes this when a user has used Webauthn to authenticate and the user presence flag was set.
|
||||
// Factor: Meta, Channel: Meta.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
//
|
||||
// W3C.WD-webauthn-20170216: https://datatracker.ietf.org/doc/html/rfc8176#ref-W3C.WD-webauthn-20170216
|
||||
AMRUserPresence = "user"
|
||||
|
||||
// AMRPersonalIdentificationNumber is an RFC8176 Authentication Method Reference Value that represents
|
||||
// authentication that included a personal Identification Number (PIN) as per RFC4949 or pattern (not restricted to
|
||||
// containing only numbers) that a user enters to unlock a key on the device. This mechanism should have a way to
|
||||
// deter an attacker from obtaining the PIN by trying repeated guesses.
|
||||
//
|
||||
// Authelia utilizes this when a user has used Webauthn to authenticate and the user verified flag was set.
|
||||
// Factor: Meta, Channel: Meta.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
//
|
||||
// RFC4949: https://datatracker.ietf.org/doc/html/rfc4949
|
||||
AMRPersonalIdentificationNumber = "pin"
|
||||
|
||||
// AMRPasswordBasedAuthentication is an RFC8176 Authentication Method Reference Value that represents password-based
|
||||
// authentication as per RFC4949.
|
||||
//
|
||||
// Authelia utilizes this when a user has performed 1FA. Factor: Know, Channel: Browser.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
//
|
||||
// RFC4949: https://datatracker.ietf.org/doc/html/rfc4949
|
||||
AMRPasswordBasedAuthentication = "pwd"
|
||||
|
||||
// AMROneTimePassword is an RFC8176 Authentication Method Reference Value that represents authentication via a
|
||||
// one-time password as per RFC4949. One-time password specifications that this authentication method applies to
|
||||
// include RFC4226 and RFC6238.
|
||||
//
|
||||
// Authelia utilizes this when a user has used TOTP to authenticate. Factor: Have, Channel: Browser.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
//
|
||||
// RFC4949: https://datatracker.ietf.org/doc/html/rfc4949
|
||||
//
|
||||
// RFC4226: https://datatracker.ietf.org/doc/html/rfc4226
|
||||
//
|
||||
// RFC6238: https://datatracker.ietf.org/doc/html/rfc6238
|
||||
AMROneTimePassword = "otp"
|
||||
|
||||
// AMRHardwareSecuredKey is an RFC8176 Authentication Method Reference Value that
|
||||
// represents authentication via a proof-of-Possession (PoP) of a hardware-secured key.
|
||||
//
|
||||
// Authelia utilizes this when a user has used Webauthn to authenticate. Factor: Have, Channel: Browser.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
AMRHardwareSecuredKey = "hwk"
|
||||
|
||||
// AMRShortMessageService is an RFC8176 Authentication Method Reference Value that
|
||||
// represents authentication via confirmation using SMS text message to the user at a registered number.
|
||||
//
|
||||
// Authelia utilizes this when a user has used Duo to authenticate. Factor: Have, Channel: Browser.
|
||||
//
|
||||
// RFC8176: https://datatracker.ietf.org/doc/html/rfc8176
|
||||
AMRShortMessageService = "sms"
|
||||
)
|
||||
|
|
|
@ -25,6 +25,7 @@ func NewOpenIDConnectStore(configuration *schema.OpenIDConnectConfiguration) (st
|
|||
AccessTokens: map[string]fosite.Requester{},
|
||||
RefreshTokens: map[string]storage.StoreRefreshToken{},
|
||||
PKCES: map[string]fosite.Requester{},
|
||||
BlacklistedJTIs: map[string]time.Time{},
|
||||
AccessTokenRequestIDs: map[string]string{},
|
||||
RefreshTokenRequestIDs: map[string]string{},
|
||||
},
|
||||
|
|
|
@ -30,7 +30,7 @@ func NewSession() (session *OpenIDSession) {
|
|||
}
|
||||
|
||||
// NewSessionWithAuthorizeRequest uses details from an AuthorizeRequester to generate an OpenIDSession.
|
||||
func NewSessionWithAuthorizeRequest(issuer, kid, subject, username string, extra map[string]interface{},
|
||||
func NewSessionWithAuthorizeRequest(issuer, kid, subject, username string, amr []string, extra map[string]interface{},
|
||||
authTime, requestedAt time.Time, requester fosite.AuthorizeRequester) (session *OpenIDSession) {
|
||||
if extra == nil {
|
||||
extra = make(map[string]interface{})
|
||||
|
@ -47,6 +47,8 @@ func NewSessionWithAuthorizeRequest(issuer, kid, subject, username string, extra
|
|||
Nonce: requester.GetRequestForm().Get("nonce"),
|
||||
Audience: requester.GetGrantedAudience(),
|
||||
Extra: extra,
|
||||
|
||||
AuthenticationMethodsReferences: amr,
|
||||
},
|
||||
Headers: &jwt.Headers{
|
||||
Extra: map[string]interface{}{
|
||||
|
|
|
@ -49,8 +49,9 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
|
|||
requested := time.Unix(1647332518, 0)
|
||||
authAt := time.Unix(1647332500, 0)
|
||||
issuer := "https://example.com"
|
||||
amr := []string{AMRPasswordBasedAuthentication}
|
||||
|
||||
session := NewSessionWithAuthorizeRequest(issuer, "primary", subject.String(), "john", extra, authAt, requested, request)
|
||||
session := NewSessionWithAuthorizeRequest(issuer, "primary", subject.String(), "john", amr, extra, authAt, requested, request)
|
||||
|
||||
require.NotNil(t, session)
|
||||
require.NotNil(t, session.Extra)
|
||||
|
@ -58,24 +59,29 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
|
|||
require.NotNil(t, session.Headers.Extra)
|
||||
require.NotNil(t, session.Claims)
|
||||
require.NotNil(t, session.Claims.Extra)
|
||||
require.NotNil(t, session.Claims.AuthenticationMethodsReferences)
|
||||
|
||||
assert.Equal(t, "abc123xyzauthelia", session.Claims.Nonce)
|
||||
assert.Equal(t, subject.String(), session.Claims.Subject)
|
||||
assert.Equal(t, subject.String(), session.Subject)
|
||||
assert.Equal(t, issuer, session.Claims.Issuer)
|
||||
assert.Equal(t, "primary", session.Headers.Get("kid"))
|
||||
assert.Equal(t, "example", session.ClientID)
|
||||
assert.Equal(t, requested, session.Claims.RequestedAt)
|
||||
assert.Equal(t, authAt, session.Claims.AuthTime)
|
||||
assert.Greater(t, session.Claims.IssuedAt.Unix(), authAt.Unix())
|
||||
assert.Equal(t, "john", session.Username)
|
||||
|
||||
require.Contains(t, session.Claims.Extra, "preferred_username")
|
||||
assert.Equal(t, "abc123xyzauthelia", session.Claims.Nonce)
|
||||
assert.Equal(t, subject.String(), session.Claims.Subject)
|
||||
assert.Equal(t, amr, session.Claims.AuthenticationMethodsReferences)
|
||||
assert.Equal(t, authAt, session.Claims.AuthTime)
|
||||
assert.Equal(t, requested, session.Claims.RequestedAt)
|
||||
assert.Equal(t, issuer, session.Claims.Issuer)
|
||||
assert.Equal(t, "john", session.Claims.Extra["preferred_username"])
|
||||
|
||||
session = NewSessionWithAuthorizeRequest(issuer, "primary", subject.String(), "john", nil, authAt, requested, request)
|
||||
assert.Equal(t, "primary", session.Headers.Get("kid"))
|
||||
|
||||
require.Contains(t, session.Claims.Extra, "preferred_username")
|
||||
|
||||
session = NewSessionWithAuthorizeRequest(issuer, "primary", subject.String(), "john", nil, nil, authAt, requested, request)
|
||||
|
||||
require.NotNil(t, session)
|
||||
require.NotNil(t, session.Claims)
|
||||
assert.NotNil(t, session.Claims.Extra)
|
||||
assert.Nil(t, session.Claims.AuthenticationMethodsReferences)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
)
|
||||
|
||||
func TestShouldInitializerSession(t *testing.T) {
|
||||
|
@ -93,9 +94,10 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
|||
AuthenticationLevel: authentication.OneFactor,
|
||||
LastActivity: timeOneFactor.Unix(),
|
||||
FirstFactorAuthnTimestamp: timeOneFactor.Unix(),
|
||||
AuthenticationMethodRefs: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true},
|
||||
}, session)
|
||||
|
||||
session.SetTwoFactor(timeTwoFactor)
|
||||
session.SetTwoFactorDuo(timeTwoFactor)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
@ -109,6 +111,7 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
|||
LastActivity: timeTwoFactor.Unix(),
|
||||
FirstFactorAuthnTimestamp: timeOneFactor.Unix(),
|
||||
SecondFactorAuthnTimestamp: timeTwoFactor.Unix(),
|
||||
AuthenticationMethodRefs: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Duo: true},
|
||||
}, session)
|
||||
|
||||
authAt, err = session.AuthenticatedTime(authorization.OneFactor)
|
||||
|
@ -124,6 +127,169 @@ func TestShouldSetSessionAuthenticationLevels(t *testing.T) {
|
|||
assert.Equal(t, timeZeroFactor, authAt)
|
||||
}
|
||||
|
||||
func TestShouldSetSessionAuthenticationLevelsAMR(t *testing.T) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.SessionConfiguration{}
|
||||
|
||||
timeOneFactor := time.Unix(1625048140, 0)
|
||||
timeTwoFactor := time.Unix(1625048150, 0)
|
||||
timeZeroFactor := time.Unix(0, 0)
|
||||
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, _ := provider.GetSession(ctx)
|
||||
|
||||
session.SetOneFactor(timeOneFactor, &authentication.UserDetails{Username: testUsername}, false)
|
||||
|
||||
err := provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
authAt, err := session.AuthenticatedTime(authorization.OneFactor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, timeOneFactor, authAt)
|
||||
|
||||
authAt, err = session.AuthenticatedTime(authorization.TwoFactor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, timeZeroFactor, authAt)
|
||||
|
||||
authAt, err = session.AuthenticatedTime(authorization.Denied)
|
||||
assert.EqualError(t, err, "invalid authorization level")
|
||||
assert.Equal(t, timeZeroFactor, authAt)
|
||||
|
||||
assert.Equal(t, UserSession{
|
||||
Username: testUsername,
|
||||
AuthenticationLevel: authentication.OneFactor,
|
||||
LastActivity: timeOneFactor.Unix(),
|
||||
FirstFactorAuthnTimestamp: timeOneFactor.Unix(),
|
||||
AuthenticationMethodRefs: oidc.AuthenticationMethodsReferences{UsernameAndPassword: true},
|
||||
}, session)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true}, session.AuthenticationMethodRefs)
|
||||
assert.True(t, session.AuthenticationMethodRefs.MultiFactorAuthentication())
|
||||
|
||||
authAt, err = session.AuthenticatedTime(authorization.OneFactor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, timeOneFactor, authAt)
|
||||
|
||||
authAt, err = session.AuthenticatedTime(authorization.TwoFactor)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, timeTwoFactor, authAt)
|
||||
|
||||
authAt, err = session.AuthenticatedTime(authorization.Denied)
|
||||
assert.EqualError(t, err, "invalid authorization level")
|
||||
assert.Equal(t, timeZeroFactor, authAt)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, true, false)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserPresence: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorWebauthn(timeTwoFactor, false, true)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
|
||||
session.SetTwoFactorTOTP(timeTwoFactor)
|
||||
|
||||
err = provider.SaveSession(ctx, session)
|
||||
require.NoError(t, err)
|
||||
|
||||
session, err = provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
oidc.AuthenticationMethodsReferences{UsernameAndPassword: true, TOTP: true, Webauthn: true, WebauthnUserVerified: true},
|
||||
session.AuthenticationMethodRefs)
|
||||
}
|
||||
|
||||
func TestShouldDestroySessionAndWipeSessionData(t *testing.T) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.SessionConfiguration{}
|
||||
|
|
|
@ -10,8 +10,9 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/logging"
|
||||
"github.com/authelia/authelia/v4/internal/model"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
)
|
||||
|
||||
// ProviderConfig is the configuration used to create the session provider.
|
||||
|
@ -37,11 +38,13 @@ type UserSession struct {
|
|||
FirstFactorAuthnTimestamp int64
|
||||
SecondFactorAuthnTimestamp int64
|
||||
|
||||
AuthenticationMethodRefs oidc.AuthenticationMethodsReferences
|
||||
|
||||
// Webauthn holds the session registration data for this session.
|
||||
Webauthn *webauthn.SessionData
|
||||
|
||||
// Represent an OIDC workflow session initiated by the client if not null.
|
||||
OIDCWorkflowSession *OIDCWorkflowSession
|
||||
OIDCWorkflowSession *model.OIDCWorkflowSession
|
||||
|
||||
// This boolean is set to true after identity verification and checked
|
||||
// while doing the query actually updating the password.
|
||||
|
@ -56,19 +59,6 @@ type Identity struct {
|
|||
Email string
|
||||
}
|
||||
|
||||
// OIDCWorkflowSession represent an OIDC workflow session.
|
||||
type OIDCWorkflowSession struct {
|
||||
ClientID string
|
||||
RequestedScopes []string
|
||||
GrantedScopes []string
|
||||
RequestedAudience []string
|
||||
GrantedAudience []string
|
||||
TargetURI string
|
||||
AuthURI string
|
||||
RequiredAuthorizationLevel authorization.Level
|
||||
CreatedTimestamp int64
|
||||
}
|
||||
|
||||
func newRedisLogger() *redisLogger {
|
||||
return &redisLogger{logger: logging.Logger()}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func NewDefaultUserSession() UserSession {
|
|||
}
|
||||
}
|
||||
|
||||
// SetOneFactor sets the expected property values for one factor authentication.
|
||||
// SetOneFactor sets the 1FA AMR's and expected property values for one factor authentication.
|
||||
func (s *UserSession) SetOneFactor(now time.Time, details *authentication.UserDetails, keepMeLoggedIn bool) {
|
||||
s.FirstFactorAuthnTimestamp = now.Unix()
|
||||
s.LastActivity = now.Unix()
|
||||
|
@ -29,15 +29,37 @@ func (s *UserSession) SetOneFactor(now time.Time, details *authentication.UserDe
|
|||
s.DisplayName = details.DisplayName
|
||||
s.Groups = details.Groups
|
||||
s.Emails = details.Emails
|
||||
|
||||
s.AuthenticationMethodRefs.UsernameAndPassword = true
|
||||
}
|
||||
|
||||
// SetTwoFactor sets the expected property values for two factor authentication.
|
||||
func (s *UserSession) SetTwoFactor(now time.Time) {
|
||||
func (s *UserSession) setTwoFactor(now time.Time) {
|
||||
s.SecondFactorAuthnTimestamp = now.Unix()
|
||||
s.LastActivity = now.Unix()
|
||||
s.AuthenticationLevel = authentication.TwoFactor
|
||||
}
|
||||
|
||||
// SetTwoFactorTOTP sets the relevant TOTP AMR's and sets the factor to 2FA.
|
||||
func (s *UserSession) SetTwoFactorTOTP(now time.Time) {
|
||||
s.setTwoFactor(now)
|
||||
s.AuthenticationMethodRefs.TOTP = true
|
||||
}
|
||||
|
||||
// SetTwoFactorDuo sets the relevant Duo AMR's and sets the factor to 2FA.
|
||||
func (s *UserSession) SetTwoFactorDuo(now time.Time) {
|
||||
s.setTwoFactor(now)
|
||||
s.AuthenticationMethodRefs.Duo = true
|
||||
}
|
||||
|
||||
// SetTwoFactorWebauthn sets the relevant Webauthn AMR's and sets the factor to 2FA.
|
||||
func (s *UserSession) SetTwoFactorWebauthn(now time.Time, userPresence, userVerified bool) {
|
||||
s.setTwoFactor(now)
|
||||
s.AuthenticationMethodRefs.Webauthn = true
|
||||
s.AuthenticationMethodRefs.WebauthnUserPresence, s.AuthenticationMethodRefs.WebauthnUserVerified = userPresence, userVerified
|
||||
|
||||
s.Webauthn = nil
|
||||
}
|
||||
|
||||
// AuthenticatedTime returns the unix timestamp this session authenticated successfully at the given level.
|
||||
func (s UserSession) AuthenticatedTime(level authorization.Level) (authenticatedTime time.Time, err error) {
|
||||
switch level {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
version: '3'
|
||||
services:
|
||||
oidc-client:
|
||||
image: ghcr.io/authelia/oidc-tester-app:master-2a82ab3
|
||||
image: ghcr.io/authelia/oidc-tester-app:master-89622a8
|
||||
command: /entrypoint.sh
|
||||
depends_on:
|
||||
- authelia-backend
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
while true;
|
||||
do
|
||||
oidc-tester-app --issuer https://login.example.com:8080 --id oidc-tester-app --secret foobar --scopes openid,profile,email --redirect-domain oidc.example.com
|
||||
oidc-tester-app --issuer https://login.example.com:8080 --id oidc-tester-app --secret foobar --scopes openid,profile,email --public-url https://oidc.example.com:8080
|
||||
sleep 5
|
||||
done
|
Loading…
Reference in New Issue