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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 272 additions and 59 deletions

View File

@ -11,7 +11,7 @@ def backport_is_backchannel(apps: Apps, schema_editor: BaseDatabaseSchemaEditor)
for model in BackchannelProvider.__subclasses__(): for model in BackchannelProvider.__subclasses__():
try: try:
for obj in model.objects.all(): for obj in model.objects.only("is_backchannel"):
obj.is_backchannel = True obj.is_backchannel = True
obj.save() obj.save()
except (DatabaseError, InternalError, ProgrammingError): except (DatabaseError, InternalError, ProgrammingError):

View File

@ -29,6 +29,7 @@ class LDAPProviderSerializer(ProviderSerializer):
"outpost_set", "outpost_set",
"search_mode", "search_mode",
"bind_mode", "bind_mode",
"mfa_support",
] ]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs extra_kwargs = ProviderSerializer.Meta.extra_kwargs
@ -99,6 +100,7 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
"gid_start_number", "gid_start_number",
"search_mode", "search_mode",
"bind_mode", "bind_mode",
"mfa_support",
] ]

View File

@ -0,0 +1,37 @@
# Generated by Django 4.1.7 on 2023-06-19 17:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_ldap", "0002_ldapprovider_bind_mode"),
]
operations = [
migrations.AddField(
model_name="ldapprovider",
name="mfa_support",
field=models.BooleanField(
default=True,
help_text="When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.",
verbose_name="MFA Support",
),
),
migrations.AlterField(
model_name="ldapprovider",
name="gid_start_number",
field=models.IntegerField(
default=4000,
help_text="The start for gidNumbers, this number is added to a number generated from the group.pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber",
),
),
migrations.AlterField(
model_name="ldapprovider",
name="uid_start_number",
field=models.IntegerField(
default=2000,
help_text="The start for uidNumbers, this number is added to the user.pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber",
),
),
]

View File

@ -50,7 +50,7 @@ class LDAPProvider(OutpostModel, BackchannelProvider):
uid_start_number = models.IntegerField( uid_start_number = models.IntegerField(
default=2000, default=2000,
help_text=_( help_text=_(
"The start for uidNumbers, this number is added to the user.Pk to make sure that the " "The start for uidNumbers, this number is added to the user.pk to make sure that the "
"numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't " "numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't "
"collide with local users uidNumber" "collide with local users uidNumber"
), ),
@ -60,7 +60,7 @@ class LDAPProvider(OutpostModel, BackchannelProvider):
default=4000, default=4000,
help_text=_( help_text=_(
"The start for gidNumbers, this number is added to a number generated from the " "The start for gidNumbers, this number is added to a number generated from the "
"group.Pk to make sure that the numbers aren't too low for POSIX groups. Default " "group.pk to make sure that the numbers aren't too low for POSIX groups. Default "
"is 4000 to ensure that we don't collide with local groups or users " "is 4000 to ensure that we don't collide with local groups or users "
"primary groups gidNumber" "primary groups gidNumber"
), ),
@ -69,6 +69,17 @@ class LDAPProvider(OutpostModel, BackchannelProvider):
bind_mode = models.TextField(default=APIAccessMode.DIRECT, choices=APIAccessMode.choices) bind_mode = models.TextField(default=APIAccessMode.DIRECT, choices=APIAccessMode.choices)
search_mode = models.TextField(default=APIAccessMode.DIRECT, choices=APIAccessMode.choices) search_mode = models.TextField(default=APIAccessMode.DIRECT, choices=APIAccessMode.choices)
mfa_support = models.BooleanField(
default=True,
verbose_name="MFA Support",
help_text=_(
"When enabled, code-based multi-factor authentication can be used by appending a "
"semicolon and the TOTP code to the password. This should only be enabled if all "
"users that will bind to this provider have a TOTP device configured, as otherwise "
"a password may incorrectly be rejected if it contains a semicolon."
),
)
@property @property
def launch_url(self) -> Optional[str]: def launch_url(self) -> Optional[str]:
"""LDAP never has a launch URL""" """LDAP never has a launch URL"""

View File

@ -3620,14 +3620,14 @@
"minimum": -2147483648, "minimum": -2147483648,
"maximum": 2147483647, "maximum": 2147483647,
"title": "Uid start number", "title": "Uid start number",
"description": "The start for uidNumbers, this number is added to the user.Pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber" "description": "The start for uidNumbers, this number is added to the user.pk to make sure that the numbers aren't too low for POSIX users. Default is 2000 to ensure that we don't collide with local users uidNumber"
}, },
"gid_start_number": { "gid_start_number": {
"type": "integer", "type": "integer",
"minimum": -2147483648, "minimum": -2147483648,
"maximum": 2147483647, "maximum": 2147483647,
"title": "Gid start number", "title": "Gid start number",
"description": "The start for gidNumbers, this number is added to a number generated from the group.Pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber" "description": "The start for gidNumbers, this number is added to a number generated from the group.pk to make sure that the numbers aren't too low for POSIX groups. Default is 4000 to ensure that we don't collide with local groups or users primary groups gidNumber"
}, },
"search_mode": { "search_mode": {
"type": "string", "type": "string",
@ -3644,6 +3644,11 @@
"cached" "cached"
], ],
"title": "Bind mode" "title": "Bind mode"
},
"mfa_support": {
"type": "boolean",
"title": "MFA Support",
"description": "When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon."
} }
}, },
"required": [] "required": []

View File

@ -14,5 +14,3 @@ 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,21 +3,10 @@ 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))
@ -25,7 +14,6 @@ func (fe *FlowExecutor) solveChallenge_Identification(challenge *api.ChallengeTy
} }
func (fe *FlowExecutor) solveChallenge_Password(challenge *api.ChallengeTypes, req api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) { func (fe *FlowExecutor) solveChallenge_Password(challenge *api.ChallengeTypes, req api.ApiFlowsExecutorSolveRequest) (api.FlowChallengeResponseRequest, error) {
fe.checkPasswordMFA()
r := api.NewPasswordChallengeResponseRequest(fe.getAnswer(StagePassword)) r := api.NewPasswordChallengeResponseRequest(fe.getAnswer(StagePassword))
return api.PasswordChallengeResponseRequestAsFlowChallengeResponseRequest(r), nil return api.PasswordChallengeResponseRequestAsFlowChallengeResponseRequest(r), nil
} }
@ -52,7 +40,6 @@ func (fe *FlowExecutor) solveChallenge_AuthenticatorValidate(challenge *api.Chal
} }
if devCh.DeviceClass == string(api.DEVICECLASSESENUM_STATIC) || if devCh.DeviceClass == string(api.DEVICECLASSESENUM_STATIC) ||
devCh.DeviceClass == string(api.DEVICECLASSESENUM_TOTP) { devCh.DeviceClass == string(api.DEVICECLASSESENUM_TOTP) {
fe.checkPasswordMFA()
// Only use code-based devices if we have a code in the entered password, // Only use code-based devices if we have a code in the entered password,
// and we haven't selected a push device yet // and we haven't selected a push device yet
if deviceChallenge == nil && fe.getAnswer(StageAuthenticatorValidate) != "" { if deviceChallenge == nil && fe.getAnswer(StageAuthenticatorValidate) != "" {

View File

@ -2,6 +2,9 @@ package direct
import ( import (
"context" "context"
"regexp"
"strconv"
"strings"
"beryju.io/ldap" "beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
@ -13,6 +16,10 @@ import (
"goauthentik.io/internal/outpost/ldap/metrics" "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) { 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{ fe := flow.NewFlowExecutor(req.Context(), db.si.GetAuthenticationFlowSlug(), db.si.GetAPIClient().GetConfig(), log.Fields{
"bindDN": req.BindDN, "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.StageIdentification] = username
fe.Answers[flow.StagePassword] = req.BindPW fe.Answers[flow.StagePassword] = req.BindPW
db.CheckPasswordMFA(fe)
passed, err := fe.Execute() passed, err := fe.Execute()
flags := flags.UserFlags{ flags := flags.UserFlags{
@ -96,3 +104,41 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
uisp.Finish() uisp.Finish()
return ldap.LDAPResultSuccess, nil 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
}

View File

@ -42,6 +42,7 @@ type ProviderInstance struct {
uidStartNumber int32 uidStartNumber int32
gidStartNumber int32 gidStartNumber int32
mfaSupport bool
} }
func (pi *ProviderInstance) GetAPIClient() *api.APIClient { func (pi *ProviderInstance) GetAPIClient() *api.APIClient {
@ -68,6 +69,10 @@ func (pi *ProviderInstance) GetOutpostName() string {
return pi.outpostName return pi.outpostName
} }
func (pi *ProviderInstance) GetMFASupport() bool {
return pi.mfaSupport
}
func (pi *ProviderInstance) GetFlags(dn string) *flags.UserFlags { func (pi *ProviderInstance) GetFlags(dn string) *flags.UserFlags {
pi.boundUsersMutex.RLock() pi.boundUsersMutex.RLock()
defer pi.boundUsersMutex.RUnlock() defer pi.boundUsersMutex.RUnlock()

View File

@ -66,7 +66,7 @@ func (ls *LDAPServer) Refresh() error {
} }
providers[idx] = &ProviderInstance{ providers[idx] = &ProviderInstance{
BaseDN: *provider.BaseDn, BaseDN: provider.GetBaseDn(),
VirtualGroupDN: virtualGroupDN, VirtualGroupDN: virtualGroupDN,
GroupDN: groupDN, GroupDN: groupDN,
UserDN: userDN, UserDN: userDN,
@ -79,8 +79,9 @@ func (ls *LDAPServer) Refresh() error {
s: ls, s: ls,
log: logger, log: logger,
tlsServerName: provider.TlsServerName, tlsServerName: provider.TlsServerName,
uidStartNumber: *provider.UidStartNumber, uidStartNumber: provider.GetUidStartNumber(),
gidStartNumber: *provider.GidStartNumber, gidStartNumber: provider.GetGidStartNumber(),
mfaSupport: provider.GetMfaSupport(),
outpostName: ls.ac.Outpost.Name, outpostName: ls.ac.Outpost.Name,
outpostPk: provider.Pk, outpostPk: provider.Pk,
} }

View File

@ -22,6 +22,7 @@ type LDAPServerInstance interface {
GetBaseGroupDN() string GetBaseGroupDN() string
GetBaseVirtualGroupDN() string GetBaseVirtualGroupDN() string
GetBaseUserDN() string GetBaseUserDN() string
GetMFASupport() bool
GetUserDN(string) string GetUserDN(string) string
GetGroupDN(string) string GetGroupDN(string) string

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-13 11:23+0000\n" "POT-Creation-Date: 2023-06-19 17:34+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -861,7 +861,7 @@ msgstr ""
#: authentik/providers/ldap/models.py:53 #: authentik/providers/ldap/models.py:53
msgid "" msgid ""
"The start for uidNumbers, this number is added to the user.Pk to make sure " "The start for uidNumbers, this number is added to the user.pk to make sure "
"that the numbers aren't too low for POSIX users. Default is 2000 to ensure " "that the numbers aren't too low for POSIX users. Default is 2000 to ensure "
"that we don't collide with local users uidNumber" "that we don't collide with local users uidNumber"
msgstr "" msgstr ""
@ -869,16 +869,25 @@ msgstr ""
#: authentik/providers/ldap/models.py:62 #: authentik/providers/ldap/models.py:62
msgid "" msgid ""
"The start for gidNumbers, this number is added to a number generated from " "The start for gidNumbers, this number is added to a number generated from "
"the group.Pk to make sure that the numbers aren't too low for POSIX groups. " "the group.pk to make sure that the numbers aren't too low for POSIX groups. "
"Default is 4000 to ensure that we don't collide with local groups or users " "Default is 4000 to ensure that we don't collide with local groups or users "
"primary groups gidNumber" "primary groups gidNumber"
msgstr "" msgstr ""
#: authentik/providers/ldap/models.py:97 #: authentik/providers/ldap/models.py:76
msgid ""
"When enabled, code-based multi-factor authentication can be used by "
"appending a semicolon and the TOTP code to the password. This should only be "
"enabled if all users that will bind to this provider have a TOTP device "
"configured, as otherwise a password may incorrectly be rejected if it "
"contains a semicolon."
msgstr ""
#: authentik/providers/ldap/models.py:108
msgid "LDAP Provider" msgid "LDAP Provider"
msgstr "" msgstr ""
#: authentik/providers/ldap/models.py:98 #: authentik/providers/ldap/models.py:109
msgid "LDAP Providers" msgid "LDAP Providers"
msgstr "" msgstr ""

View File

@ -30796,7 +30796,7 @@ components:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for uidNumbers, this number is added to the user.Pk description: The start for uidNumbers, this number is added to the user.pk
to make sure that the numbers aren't too low for POSIX users. Default to make sure that the numbers aren't too low for POSIX users. Default
is 2000 to ensure that we don't collide with local users uidNumber is 2000 to ensure that we don't collide with local users uidNumber
gid_start_number: gid_start_number:
@ -30804,13 +30804,20 @@ components:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for gidNumbers, this number is added to a number description: The start for gidNumbers, this number is added to a number
generated from the group.Pk to make sure that the numbers aren't too low generated from the group.pk to make sure that the numbers aren't too low
for POSIX groups. Default is 4000 to ensure that we don't collide with for POSIX groups. Default is 4000 to ensure that we don't collide with
local groups or users primary groups gidNumber local groups or users primary groups gidNumber
search_mode: search_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
bind_mode: bind_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
mfa_support:
type: boolean
description: When enabled, code-based multi-factor authentication can be
used by appending a semicolon and the TOTP code to the password. This
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
required: required:
- application_slug - application_slug
- bind_flow_slug - bind_flow_slug
@ -30966,7 +30973,7 @@ components:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for uidNumbers, this number is added to the user.Pk description: The start for uidNumbers, this number is added to the user.pk
to make sure that the numbers aren't too low for POSIX users. Default to make sure that the numbers aren't too low for POSIX users. Default
is 2000 to ensure that we don't collide with local users uidNumber is 2000 to ensure that we don't collide with local users uidNumber
gid_start_number: gid_start_number:
@ -30974,7 +30981,7 @@ components:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for gidNumbers, this number is added to a number description: The start for gidNumbers, this number is added to a number
generated from the group.Pk to make sure that the numbers aren't too low generated from the group.pk to make sure that the numbers aren't too low
for POSIX groups. Default is 4000 to ensure that we don't collide with for POSIX groups. Default is 4000 to ensure that we don't collide with
local groups or users primary groups gidNumber local groups or users primary groups gidNumber
outpost_set: outpost_set:
@ -30986,6 +30993,13 @@ components:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
bind_mode: bind_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
mfa_support:
type: boolean
description: When enabled, code-based multi-factor authentication can be
used by appending a semicolon and the TOTP code to the password. This
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
required: required:
- assigned_application_name - assigned_application_name
- assigned_application_slug - assigned_application_slug
@ -31041,7 +31055,7 @@ components:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for uidNumbers, this number is added to the user.Pk description: The start for uidNumbers, this number is added to the user.pk
to make sure that the numbers aren't too low for POSIX users. Default to make sure that the numbers aren't too low for POSIX users. Default
is 2000 to ensure that we don't collide with local users uidNumber is 2000 to ensure that we don't collide with local users uidNumber
gid_start_number: gid_start_number:
@ -31049,13 +31063,20 @@ components:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for gidNumbers, this number is added to a number description: The start for gidNumbers, this number is added to a number
generated from the group.Pk to make sure that the numbers aren't too low generated from the group.pk to make sure that the numbers aren't too low
for POSIX groups. Default is 4000 to ensure that we don't collide with for POSIX groups. Default is 4000 to ensure that we don't collide with
local groups or users primary groups gidNumber local groups or users primary groups gidNumber
search_mode: search_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
bind_mode: bind_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
mfa_support:
type: boolean
description: When enabled, code-based multi-factor authentication can be
used by appending a semicolon and the TOTP code to the password. This
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
required: required:
- authorization_flow - authorization_flow
- name - name
@ -36721,7 +36742,7 @@ components:
type: integer type: integer
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for uidNumbers, this number is added to the user.Pk description: The start for uidNumbers, this number is added to the user.pk
to make sure that the numbers aren't too low for POSIX users. Default to make sure that the numbers aren't too low for POSIX users. Default
is 2000 to ensure that we don't collide with local users uidNumber is 2000 to ensure that we don't collide with local users uidNumber
gid_start_number: gid_start_number:
@ -36729,13 +36750,20 @@ components:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
description: The start for gidNumbers, this number is added to a number description: The start for gidNumbers, this number is added to a number
generated from the group.Pk to make sure that the numbers aren't too low generated from the group.pk to make sure that the numbers aren't too low
for POSIX groups. Default is 4000 to ensure that we don't collide with for POSIX groups. Default is 4000 to ensure that we don't collide with
local groups or users primary groups gidNumber local groups or users primary groups gidNumber
search_mode: search_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
bind_mode: bind_mode:
$ref: '#/components/schemas/LDAPAPIAccessMode' $ref: '#/components/schemas/LDAPAPIAccessMode'
mfa_support:
type: boolean
description: When enabled, code-based multi-factor authentication can be
used by appending a semicolon and the TOTP code to the password. This
should only be enabled if all users that will bind to this provider have
a TOTP device configured, as otherwise a password may incorrectly be rejected
if it contains a semicolon.
PatchedLDAPSourceRequest: PatchedLDAPSourceRequest:
type: object type: object
description: LDAP Source Serializer description: LDAP Source Serializer

View File

@ -187,6 +187,27 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
${msg("Configure how the outpost queries the core authentik server's users.")} ${msg("Configure how the outpost queries the core authentik server's users.")}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="mfaSupport">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(this.instance?.mfaSupport, true)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">${msg("Code-based MFA Support")}</span>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}> <ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span> <span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">

View File

@ -5741,6 +5741,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6057,6 +6057,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5649,6 +5649,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5756,6 +5756,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5888,6 +5888,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5992,6 +5992,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5639,6 +5639,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2"> <?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext"> <file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body> <body>
<trans-unit id="s4caed5b7a7e5d89b"> <trans-unit id="s4caed5b7a7e5d89b">
@ -618,9 +618,9 @@
</trans-unit> </trans-unit>
<trans-unit id="saa0e2675da69651b"> <trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source> <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>未找到 URL &quot; <target>未找到 URL "
<x id="0" equiv-text="${this.url}"/>&quot;。</target> <x id="0" equiv-text="${this.url}"/>"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s58cd9c2fe836d9c6"> <trans-unit id="s58cd9c2fe836d9c6">
@ -1072,8 +1072,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa8384c9c26731f83"> <trans-unit id="sa8384c9c26731f83">
<source>To allow any redirect URI, set this value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source> <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
<target>要允许任何重定向 URI请将此值设置为 &quot;.*&quot;。请注意这可能带来的安全影响。</target> <target>要允许任何重定向 URI请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
</trans-unit> </trans-unit>
<trans-unit id="s55787f4dfcdce52b"> <trans-unit id="s55787f4dfcdce52b">
@ -1819,8 +1819,8 @@
</trans-unit> </trans-unit>
<trans-unit id="sa90b7809586c35ce"> <trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source> <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 &quot;fa-test&quot;。</target> <target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s0410779cb47de312"> <trans-unit id="s0410779cb47de312">
@ -3248,8 +3248,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s76768bebabb7d543"> <trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>包含组成员的字段。请注意,如果使用 &quot;memberUid&quot; 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target> <target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
</trans-unit> </trans-unit>
<trans-unit id="s026555347e589f0e"> <trans-unit id="s026555347e589f0e">
@ -4046,8 +4046,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s7b1fba26d245cb1c"> <trans-unit id="s7b1fba26d245cb1c">
<source>When using an external logging solution for archiving, this can be set to &quot;minutes=5&quot;.</source> <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 &quot;minutes=5&quot;。</target> <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s44536d20bb5c8257"> <trans-unit id="s44536d20bb5c8257">
@ -4056,8 +4056,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s3bb51cabb02b997e"> <trans-unit id="s3bb51cabb02b997e">
<source>Format: &quot;weeks=3;days=2;hours=3,seconds=2&quot;.</source> <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
<target>格式:&quot;weeks=3;days=2;hours=3,seconds=2&quot;。</target> <target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
</trans-unit> </trans-unit>
<trans-unit id="s04bfd02201db5ab8"> <trans-unit id="s04bfd02201db5ab8">
@ -4253,10 +4253,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sa95a538bfbb86111"> <trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source> <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>您确定要更新 <target>您确定要更新
<x id="0" equiv-text="${this.objectLabel}"/>&quot; <x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>&quot; 吗?</target> <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
</trans-unit> </trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6"> <trans-unit id="sc92d7cfb6ee1fec6">
@ -5372,7 +5372,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="sdf1d8edef27236f0"> <trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source> <source>A "roaming" authenticator, like a YubiKey</source>
<target>像 YubiKey 这样的“漫游”身份验证器</target> <target>像 YubiKey 这样的“漫游”身份验证器</target>
</trans-unit> </trans-unit>
@ -5712,10 +5712,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s2d5f69929bb7221d"> <trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${prompt.name}"/> (&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${prompt.type}"/>)</source> <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
<target> <target>
<x id="0" equiv-text="${prompt.name}"/>&quot; <x id="0" equiv-text="${prompt.name}"/>"
<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;,类型为 <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
<x id="2" equiv-text="${prompt.type}"/></target> <x id="2" equiv-text="${prompt.type}"/></target>
</trans-unit> </trans-unit>
@ -5764,7 +5764,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit> </trans-unit>
<trans-unit id="s1608b2f94fa0dbd4"> <trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source> <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target> <target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target>
</trans-unit> </trans-unit>
@ -7572,7 +7572,13 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
<target>匹配选定模型创建的事件。如果留空,则匹配所有模型。</target> <target>匹配选定模型创建的事件。如果留空,则匹配所有模型。</target>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -5694,6 +5694,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5693,6 +5693,12 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s3ba9b8aeb686d9f7"> <trans-unit id="s3ba9b8aeb686d9f7">
<source>Match events created by selected model. When left empty, all models are matched.</source> <source>Match events created by selected model. When left empty, all models are matched.</source>
</trans-unit>
<trans-unit id="s254d527e3a53dbb7">
<source>Code-based MFA Support</source>
</trans-unit>
<trans-unit id="s1889ba2eaeec2f1e">
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -80,6 +80,8 @@ The following stages are supported:
Note: Authenticator validation currently only supports DUO, TOTP and static authenticators. Note: Authenticator validation currently only supports DUO, TOTP and static authenticators.
Starting with authentik 2023.6, code-based authenticators are only supported when _Code-based MFA Support_ is enabled in the provider. When enabled, all users that will bind to the LDAP provider should have a TOTP device configured, as otherwise a password might be incorrectly rejected when semicolons are used in the password.
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`. 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. SMS-based authenticators are not supported as they require a code to be sent from authentik, which is not possible during the bind.