providers/ldap: improve password totp detection (#6006)

* providers/ldap: improve password totp detection

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add flag for totp mfa support

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* keep support for static tokens

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix migrations

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2023-06-20 12:09:13 +02:00
committed by GitHub
parent 962cbf9f6a
commit 01311929d1
25 changed files with 272 additions and 59 deletions

View File

@ -2,6 +2,9 @@ package direct
import (
"context"
"regexp"
"strconv"
"strings"
"beryju.io/ldap"
"github.com/getsentry/sentry-go"
@ -13,6 +16,10 @@ import (
"goauthentik.io/internal/outpost/ldap/metrics"
)
const CodePasswordSeparator = ";"
var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`)
func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResultCode, error) {
fe := flow.NewFlowExecutor(req.Context(), db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"bindDN": req.BindDN,
@ -24,6 +31,7 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
fe.Answers[flow.StageIdentification] = username
fe.Answers[flow.StagePassword] = req.BindPW
db.CheckPasswordMFA(fe)
passed, err := fe.Execute()
flags := flags.UserFlags{
@ -96,3 +104,41 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
uisp.Finish()
return ldap.LDAPResultSuccess, nil
}
func (db *DirectBinder) CheckPasswordMFA(fe *flow.FlowExecutor) {
if !db.si.GetMFASupport() {
return
}
password := fe.Answers[flow.StagePassword]
// We already have an authenticator answer
if fe.Answers[flow.StageAuthenticatorValidate] != "" {
return
}
// password doesn't contain the separator
if !strings.Contains(password, CodePasswordSeparator) {
return
}
// password ends with the separator, so it won't contain an answer
if strings.HasSuffix(password, CodePasswordSeparator) {
return
}
idx := strings.LastIndex(password, CodePasswordSeparator)
authenticator := password[idx+1:]
// Authenticator is either 6 chars (totp code) or 8 chars (long totp or static)
if len(authenticator) == 6 {
// authenticator answer isn't purely numerical, so won't be value
if _, err := strconv.Atoi(authenticator); err != nil {
return
}
} else if len(authenticator) == 8 {
// 8 chars can be a long totp or static token, so it needs to be alphanumerical
if !alphaNum.MatchString(authenticator) {
return
}
} else {
// Any other length, doesn't contain an answer
return
}
fe.Answers[flow.StagePassword] = password[:idx]
fe.Answers[flow.StageAuthenticatorValidate] = authenticator
}