providers/ldap: add code-MFA support for ldap provider (#4354)

* add code support for ldap provider

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* only try to extract code when auth validator stage is encountered

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* use parseint instead

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2023-01-05 18:32:06 +01:00 committed by GitHub
parent 06f67c738c
commit 2604dc14fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 11 deletions

View File

@ -13,3 +13,5 @@ const (
HeaderAuthentikRemoteIP = "X-authentik-remote-ip" HeaderAuthentikRemoteIP = "X-authentik-remote-ip"
HeaderAuthentikOutpostToken = "X-authentik-outpost-token" HeaderAuthentikOutpostToken = "X-authentik-outpost-token"
) )
const CodePasswordSeparator = ";"

View File

@ -3,10 +3,21 @@ package flow
import ( import (
"errors" "errors"
"strconv" "strconv"
"strings"
"goauthentik.io/api/v3" "goauthentik.io/api/v3"
) )
func (fe *FlowExecutor) checkPasswordMFA() {
password := fe.getAnswer(StagePassword)
if !strings.Contains(password, CodePasswordSeparator) || fe.Answers[StageAuthenticatorValidate] != "" {
return
}
idx := strings.LastIndex(password, CodePasswordSeparator)
fe.Answers[StagePassword] = password[:idx]
fe.Answers[StageAuthenticatorValidate] = password[idx+1:]
}
func (fe *FlowExecutor) solveChallenge_Identification(challenge *api.ChallengeTypes, req api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) { func (fe *FlowExecutor) solveChallenge_Identification(challenge *api.ChallengeTypes, req api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) {
r := api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification)) r := api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))
r.SetPassword(fe.getAnswer(StagePassword)) r.SetPassword(fe.getAnswer(StagePassword))
@ -19,23 +30,35 @@ func (fe *FlowExecutor) solveChallenge_Password(challenge *api.ChallengeTypes, r
} }
func (fe *FlowExecutor) solveChallenge_AuthenticatorValidate(challenge *api.ChallengeTypes, req api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) { func (fe *FlowExecutor) solveChallenge_AuthenticatorValidate(challenge *api.ChallengeTypes, req api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) {
// We only support duo as authenticator, check if that's allowed // We only support duo and code-based authenticators, check if that's allowed
var deviceChallenge *api.DeviceChallenge var deviceChallenge *api.DeviceChallenge
inner := api.NewAuthenticatorValidationChallengeResponseRequest()
for _, devCh := range challenge.AuthenticatorValidationChallenge.DeviceChallenges { for _, devCh := range challenge.AuthenticatorValidationChallenge.DeviceChallenges {
if devCh.DeviceClass == string(api.DEVICECLASSESENUM_DUO) { if devCh.DeviceClass == string(api.DEVICECLASSESENUM_DUO) {
deviceChallenge = &devCh deviceChallenge = &devCh
devId, err := strconv.ParseInt(deviceChallenge.DeviceUid, 10, 32)
if err != nil {
return api.FlowChallengeResponseRequest{}, errors.New("failed to convert duo device id to int")
}
devId32 := int32(devId)
inner.SelectedChallenge = (*api.DeviceChallengeRequest)(deviceChallenge)
inner.Duo = &devId32
}
if devCh.DeviceClass == string(api.DEVICECLASSESENUM_STATIC) ||
devCh.DeviceClass == string(api.DEVICECLASSESENUM_TOTP) {
fe.checkPasswordMFA()
// Only use code-based devices if we have a code in the entered password,
// and we haven't selected a push device yet
if deviceChallenge == nil && fe.getAnswer(StageAuthenticatorValidate) != "" {
deviceChallenge = &devCh
inner.SelectedChallenge = (*api.DeviceChallengeRequest)(deviceChallenge)
code := fe.getAnswer(StageAuthenticatorValidate)
inner.Code = &code
}
} }
} }
if deviceChallenge == nil { if deviceChallenge == nil {
return api.FlowChallengeResponseRequest{}, errors.New("no compatible authenticator class found") return api.FlowChallengeResponseRequest{}, errors.New("no compatible authenticator class found")
} }
devId, err := strconv.Atoi(deviceChallenge.DeviceUid)
if err != nil {
return api.FlowChallengeResponseRequest{}, errors.New("failed to convert duo device id to int")
}
devId32 := int32(devId)
inner := api.NewAuthenticatorValidationChallengeResponseRequest()
inner.SelectedChallenge = (*api.DeviceChallengeRequest)(deviceChallenge)
inner.Duo = &devId32
return api.AuthenticatorValidationChallengeResponseRequestAsFlowChallengeResponseRequest(inner), nil return api.AuthenticatorValidationChallengeResponseRequestAsFlowChallengeResponseRequest(inner), nil
} }

View File

@ -72,11 +72,15 @@ The following stages are supported:
- [Password](../../flow/stages/password/index.md) - [Password](../../flow/stages/password/index.md)
- [Authenticator validation](../../flow/stages/authenticator_validate/index.md) - [Authenticator validation](../../flow/stages/authenticator_validate/index.md)
Note: Authenticator validation currently only supports DUO devices Note: Authenticator validation currently only supports DUO, TOTP and static authenticators.
For code-based authenticators, the code must be given as part of the bind password, separated by a semicolon. For example for the password `example-password` and the code `123456`, the input must be `example-password;123456`.
SMS-based authenticators are not supported as they require a code to be sent from authentik, which is not possible during the bind.
#### Direct bind #### Direct bind
In this mode, the outpost will always execute the configured flow when a new bind request arrives. In this mode, the outpost will always execute the configured flow when a new bind request is received.
#### Cached bind #### Cached bind