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:
parent
06f67c738c
commit
2604dc14fe
|
@ -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 = ";"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Reference in New Issue