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:
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user