providers/ldap: rework Schema and DSE (#5838)
* rework Root DSE Signed-off-by: Jens Langhammer <jens@goauthentik.io> * always parse filter objectClass Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start adding LDAP Schema Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more schema Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update schema more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix cn for schema Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only include main DN in namingContexts Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use schema from gh Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add description Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add response filtering Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix response filtering Signed-off-by: Jens Langhammer <jens@goauthentik.io> * don't return rootDSE entry when searching for singleLevel Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove currentTime Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix attribute filtering Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * set SINGLE-VALUE Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix numbers Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
bf1a363124
commit
54ef88a6fa
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
type Binder interface {
|
||||
GetUsername(string) (string, error)
|
||||
GetUsername(dn string) (string, error)
|
||||
Bind(username string, req *Request) (ldap.LDAPResultCode, error)
|
||||
Unbind(username string, req *Request) (ldap.LDAPResultCode, error)
|
||||
TimerFlowCacheExpiry(context.Context)
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
package constants
|
||||
|
||||
const OC = "objectClass"
|
||||
|
||||
const (
|
||||
OCTop = "top"
|
||||
OCDomain = "domain"
|
||||
OCNSContainer = "nsContainer"
|
||||
OCSubSchema = "subschema"
|
||||
)
|
||||
|
||||
const (
|
||||
SearchAttributeNone = "1.1"
|
||||
SearchAttributeAllUser = "*"
|
||||
SearchAttributeAllOperational = "+"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -2,16 +2,13 @@ package ldap
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"beryju.io/ldap"
|
||||
"github.com/go-openapi/strfmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"goauthentik.io/api/v3"
|
||||
"goauthentik.io/internal/constants"
|
||||
"goauthentik.io/internal/outpost/ldap/bind"
|
||||
ldapConstants "goauthentik.io/internal/outpost/ldap/constants"
|
||||
"goauthentik.io/internal/outpost/ldap/flags"
|
||||
|
@ -107,43 +104,6 @@ func (pi *ProviderInstance) GetSearchAllowedGroups() []*strfmt.UUID {
|
|||
return pi.searchAllowedGroups
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) GetBaseEntry() *ldap.Entry {
|
||||
return &ldap.Entry{
|
||||
DN: pi.GetBaseDN(),
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "distinguishedName",
|
||||
Values: []string{pi.GetBaseDN()},
|
||||
},
|
||||
{
|
||||
Name: "objectClass",
|
||||
Values: []string{ldapConstants.OCTop, ldapConstants.OCDomain},
|
||||
},
|
||||
{
|
||||
Name: "supportedLDAPVersion",
|
||||
Values: []string{"3"},
|
||||
},
|
||||
{
|
||||
Name: "namingContexts",
|
||||
Values: []string{
|
||||
pi.GetBaseDN(),
|
||||
pi.GetBaseUserDN(),
|
||||
pi.GetBaseGroupDN(),
|
||||
pi.GetBaseVirtualGroupDN(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "vendorName",
|
||||
Values: []string{"goauthentik.io"},
|
||||
},
|
||||
{
|
||||
Name: "vendorVersion",
|
||||
Values: []string{fmt.Sprintf("authentik LDAP Outpost Version %s", constants.FullVersion())},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) GetNeededObjects(scope int, baseDN string, filterOC string) (bool, bool) {
|
||||
needUsers := false
|
||||
needGroups := false
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"beryju.io/ldap"
|
||||
"github.com/getsentry/sentry-go"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/internal/outpost/ldap/constants"
|
||||
"goauthentik.io/internal/outpost/ldap/metrics"
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
)
|
||||
|
@ -36,38 +34,20 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
|
|||
sentry.CaptureException(err.(error))
|
||||
}()
|
||||
|
||||
if searchReq.BaseDN == "" {
|
||||
return ldap.ServerSearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: "",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "objectClass",
|
||||
Values: []string{"top", "OpenLDAProotDSE"},
|
||||
},
|
||||
{
|
||||
Name: "subschemaSubentry",
|
||||
Values: []string{"cn=subschema"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
|
||||
}, nil
|
||||
selectedProvider := ls.providerForRequest(req)
|
||||
if selectedProvider == nil {
|
||||
return ls.fallbackRootDSE(req)
|
||||
}
|
||||
bd, err := goldap.ParseDN(strings.ToLower(searchReq.BaseDN))
|
||||
selectedApp = selectedProvider.GetAppSlug()
|
||||
result, err := ls.searchRoute(req, selectedProvider)
|
||||
if err != nil {
|
||||
req.Log().WithError(err).Info("failed to parse basedn")
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("invalid DN")
|
||||
}
|
||||
for _, provider := range ls.providers {
|
||||
providerBase, _ := goldap.ParseDN(strings.ToLower(provider.BaseDN))
|
||||
if providerBase.AncestorOf(bd) || providerBase.Equal(bd) {
|
||||
selectedApp = provider.GetAppSlug()
|
||||
return provider.searcher.Search(req)
|
||||
return result, nil
|
||||
}
|
||||
return ls.filterResultAttributes(req, result), nil
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) fallbackRootDSE(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
req.Log().Trace("returning fallback Root DSE")
|
||||
return ldap.ServerSearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
|
@ -75,15 +55,30 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
|
|||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "objectClass",
|
||||
Values: []string{"top", "OpenLDAProotDSE"},
|
||||
Values: []string{constants.OCTop},
|
||||
},
|
||||
{
|
||||
Name: "entryDN",
|
||||
Values: []string{""},
|
||||
},
|
||||
{
|
||||
Name: "subschemaSubentry",
|
||||
Values: []string{"cn=subschema"},
|
||||
},
|
||||
{
|
||||
Name: "namingContexts",
|
||||
Values: []string{},
|
||||
},
|
||||
{
|
||||
Name: "description",
|
||||
Values: []string{
|
||||
"This LDAP server requires an authenticated session.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
|
|
@ -2,40 +2,51 @@ package direct
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"beryju.io/ldap"
|
||||
"goauthentik.io/internal/constants"
|
||||
ldapConstants "goauthentik.io/internal/outpost/ldap/constants"
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
)
|
||||
|
||||
func (ds *DirectSearcher) SearchBase(req *search.Request, authz bool) (ldap.ServerSearchResult, error) {
|
||||
dn := ""
|
||||
if authz {
|
||||
dn = req.SearchRequest.BaseDN
|
||||
func (ds *DirectSearcher) SearchBase(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
if req.Scope == ldap.ScopeSingleLevel {
|
||||
return ldap.ServerSearchResult{
|
||||
ResultCode: ldap.LDAPResultNoSuchObject,
|
||||
}, nil
|
||||
}
|
||||
return ldap.ServerSearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: dn,
|
||||
DN: "",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "distinguishedName",
|
||||
Values: []string{ds.si.GetBaseDN()},
|
||||
Name: "objectClass",
|
||||
Values: []string{ldapConstants.OCTop},
|
||||
},
|
||||
{
|
||||
Name: "objectClass",
|
||||
Values: []string{"top", "domain"},
|
||||
Name: "entryDN",
|
||||
Values: []string{""},
|
||||
},
|
||||
{
|
||||
Name: "supportedLDAPVersion",
|
||||
Values: []string{"3"},
|
||||
},
|
||||
{
|
||||
Name: "subschemaSubentry",
|
||||
Values: []string{"cn=subschema"},
|
||||
},
|
||||
{
|
||||
Name: "namingContexts",
|
||||
Values: []string{
|
||||
ds.si.GetBaseDN(),
|
||||
ds.si.GetBaseUserDN(),
|
||||
ds.si.GetBaseGroupDN(),
|
||||
strings.ToLower(ds.si.GetBaseDN()),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rootDomainNamingContext",
|
||||
Values: []string{
|
||||
strings.ToLower(ds.si.GetBaseDN()),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -31,7 +31,6 @@ func NewDirectSearcher(si server.LDAPServerInstance) *DirectSearcher {
|
|||
si: si,
|
||||
log: log.WithField("logger", "authentik.outpost.ldap.searcher.direct"),
|
||||
}
|
||||
ds.log.Info("initialised direct searcher")
|
||||
return ds
|
||||
}
|
||||
|
||||
|
@ -39,16 +38,6 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access")
|
||||
baseDN := ds.si.GetBaseDN()
|
||||
|
||||
filterOC, err := ldap.GetFilterObjectClass(req.Filter)
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": ds.si.GetOutpostName(),
|
||||
"type": "search",
|
||||
"reason": "filter_parse_fail",
|
||||
"app": ds.si.GetAppSlug(),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter)
|
||||
}
|
||||
if len(req.BindDN) < 1 {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": ds.si.GetOutpostName(),
|
||||
|
@ -99,11 +88,17 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
c.GetConfig().AddDefaultHeader("X-authentik-outpost-ldap-query", req.Filter)
|
||||
|
||||
scope := req.SearchRequest.Scope
|
||||
needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, filterOC)
|
||||
needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, req.FilterObjectClass)
|
||||
|
||||
if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) {
|
||||
if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) {
|
||||
entries = append(entries, ds.si.GetBaseEntry())
|
||||
if utils.IncludeObjectClass(req.FilterObjectClass, constants.GetDomainOCs()) {
|
||||
rootEntries, _ := ds.SearchBase(req)
|
||||
// Since `SearchBase` returns entries for the root DN, we need to go through the
|
||||
// entries and update the base DN
|
||||
for _, e := range rootEntries.Entries {
|
||||
e.DN = ds.si.GetBaseDN()
|
||||
entries = append(entries, e)
|
||||
}
|
||||
}
|
||||
|
||||
scope -= 1 // Bring it from WholeSubtree to SingleLevel and so on
|
||||
|
@ -197,12 +192,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseUserDN())) {
|
||||
singleu := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseUserDN())
|
||||
|
||||
if !singleu && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseUserDN(), constants.OUUsers))
|
||||
if !singleu && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(req.FilterObjectClass, ds.si.GetBaseUserDN(), constants.OUUsers))
|
||||
scope -= 1
|
||||
}
|
||||
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) {
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetUserOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := ds.si.UserEntry(u)
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singleu {
|
||||
|
@ -217,12 +212,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseGroupDN())) {
|
||||
singleg := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseGroupDN())
|
||||
|
||||
if !singleg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseGroupDN(), constants.OUGroups))
|
||||
if !singleg && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(req.FilterObjectClass, ds.si.GetBaseGroupDN(), constants.OUGroups))
|
||||
scope -= 1
|
||||
}
|
||||
|
||||
if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) {
|
||||
if scope >= 0 && groups != nil && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetGroupOCs()) {
|
||||
for _, g := range *groups {
|
||||
entry := group.FromAPIGroup(g, ds.si).Entry()
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singleg {
|
||||
|
@ -237,12 +232,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseVirtualGroupDN())) {
|
||||
singlevg := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseVirtualGroupDN())
|
||||
|
||||
if !singlevg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups))
|
||||
if !singlevg && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(req.FilterObjectClass, ds.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups))
|
||||
scope -= 1
|
||||
}
|
||||
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) {
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetVirtualGroupOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := group.FromAPIUser(u, ds.si).Entry()
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package direct
|
||||
|
||||
import (
|
||||
"beryju.io/ldap"
|
||||
"goauthentik.io/internal/outpost/ldap/constants"
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
)
|
||||
|
||||
func (ds *DirectSearcher) SearchSubschema(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
return ldap.ServerSearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: "cn=subschema",
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "cn",
|
||||
Values: []string{"subschema"},
|
||||
},
|
||||
{
|
||||
Name: constants.OC,
|
||||
Values: []string{constants.OCTop, "subSchema"},
|
||||
},
|
||||
{
|
||||
Name: "ldapSyntaxes",
|
||||
Values: []string{
|
||||
"( 1.3.6.1.4.1.1466.115.121.1.40 DESC 'Octet String' )",
|
||||
"( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' )",
|
||||
"( 1.3.6.1.4.1.1466.115.121.1.7 DESC 'Boolean' )",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "objectClasses",
|
||||
Values: []string{
|
||||
"( 2.5.6.0 NAME 'top' ABSTRACT MUST ( objectClass ) MAY (cn $ description $ displayName $ memberOf $ name ) )",
|
||||
"( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( cn ) MAY (sn $ telephoneNumber ) )",
|
||||
"( 2.5.6.7 NAME 'organizationalPerson' SUP person STRUCTURAL MAY (c $ l $ o $ ou $ title $ givenName $ co $ department $ company $ division $ mail $ mobile $ telephoneNumber ) )",
|
||||
"( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST (cn $ member ) MAY (o $ ou ) )",
|
||||
"( 1.2.840.113556.1.5.9 NAME 'user' SUP organizationalPerson STRUCTURAL MAY ( name $ displayName $ uid $ mail ) )",
|
||||
"( 1.3.6.1.1.1.2.0 NAME 'posixAccount' SUP top AUXILIARY MAY (cn $ description $ homeDirectory $ uid $ uidNumber $ gidNumber ) )",
|
||||
"( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' AUX ( posixAccount ) MUST ( sAMAccountName ) MAY ( uidNumber $ gidNumber ))",
|
||||
// Custom attributes
|
||||
// Temporarily use 1.3.6.1.4.1.26027.1.1 as a base
|
||||
// https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid
|
||||
"( 1.3.6.1.4.1.26027.1.1.1 NAME 'goauthentik.io/ldap/user' SUP organizationalPerson STRUCTURAL MAY ( ak-active $ sAMAccountName $ goauthentikio-user-sources $ goauthentik.io/user/sources $ goauthentik.io/ldap/active $ goauthentik.io/ldap/superuser $ goauthentikio-user-override-ips $ goauthentikio-user-service-account ) )",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "attributeTypes",
|
||||
Values: []string{
|
||||
"( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
|
||||
"( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts' DESC 'RFC4512: naming contexts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )",
|
||||
"( 2.5.18.10 NAME 'subschemaSubentry' DESC 'RFC4512: name of controlling subschema entry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
|
||||
"( 1.3.6.1.4.1.1466.101.120.15 NAME 'supportedLDAPVersion' DESC 'RFC4512: supported LDAP versions' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation )",
|
||||
"( 1.3.6.1.1.20 NAME 'entryDN' DESC 'DN of the entry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
|
||||
"( 1.3.6.1.1.4 NAME 'vendorName' DESC 'RFC3045: name of implementation vendor' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
|
||||
"( 1.3.6.1.1.5 NAME 'vendorVersion' DESC 'RFC3045: version of implementation' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
|
||||
"( 0.9.2342.19200300.100.1.1 NAME 'uid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 0.9.2342.19200300.100.1.3 NAME 'mail' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 0.9.2342.19200300.100.1.41 NAME 'mobile' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.2.102 NAME 'memberOf' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' NO-USER-MODIFICATION )",
|
||||
"( 1.2.840.113556.1.2.13 NAME 'displayName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.4.1 NAME 'name' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE NO-USER-MODIFICATION )",
|
||||
"( 1.2.840.113556.1.2.131 NAME 'co' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.2.141 NAME 'department' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.2.146 NAME 'company' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.4.44 NAME 'homeDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.4.221 NAME 'sAMAccountName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.2.840.113556.1.4.261 NAME 'division' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 1.3.6.1.1.1.1.0 NAME 'uidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
|
||||
"( 1.3.6.1.1.1.1.1 NAME 'gidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )",
|
||||
"( 2.5.4.6 NAME 'c' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.7 NAME 'l' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.10 NAME 'o' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )",
|
||||
"( 2.5.4.11 NAME 'ou' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )",
|
||||
"( 2.5.4.20 NAME 'telephoneNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.42 NAME 'givenName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.0 NAME 'objectClass' SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' NO-USER-MODIFICATION )",
|
||||
"( 2.5.4.3 NAME 'cn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.4 NAME 'sn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.12 NAME 'title' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )",
|
||||
"( 2.5.4.13 NAME 'description' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )",
|
||||
"( 2.5.4.31 NAME 'member' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )",
|
||||
// Custom attributes
|
||||
// Temporarily use 1.3.6.1.4.1.26027.1.1 as a base
|
||||
// https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid
|
||||
"( 1.3.6.1.4.1.26027.1.1.2 NAME ( 'goauthentik.io/ldap/superuser' 'ak-superuser' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
||||
"( 1.3.6.1.4.1.26027.1.1.3 NAME ( 'goauthentik.io/ldap/active' 'ak-active' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
||||
"( 1.3.6.1.4.1.26027.1.1.4 NAME 'goauthentikio-user-override-ips' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
||||
"( 1.3.6.1.4.1.26027.1.1.5 NAME 'goauthentikio-user-service-account' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE' )",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -15,6 +15,7 @@ import (
|
|||
"goauthentik.io/internal/outpost/ldap/group"
|
||||
"goauthentik.io/internal/outpost/ldap/metrics"
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
"goauthentik.io/internal/outpost/ldap/search/direct"
|
||||
"goauthentik.io/internal/outpost/ldap/server"
|
||||
"goauthentik.io/internal/outpost/ldap/utils"
|
||||
"goauthentik.io/internal/outpost/ldap/utils/paginator"
|
||||
|
@ -23,6 +24,7 @@ import (
|
|||
type MemorySearcher struct {
|
||||
si server.LDAPServerInstance
|
||||
log *log.Entry
|
||||
ds *direct.DirectSearcher
|
||||
|
||||
users []api.User
|
||||
groups []api.Group
|
||||
|
@ -32,6 +34,7 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
|
|||
ms := &MemorySearcher{
|
||||
si: si,
|
||||
log: log.WithField("logger", "authentik.outpost.ldap.searcher.memory"),
|
||||
ds: direct.NewDirectSearcher(si),
|
||||
}
|
||||
ms.log.Debug("initialised memory searcher")
|
||||
ms.users = paginator.FetchUsers(ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()))
|
||||
|
@ -39,20 +42,18 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
|
|||
return ms
|
||||
}
|
||||
|
||||
func (ms *MemorySearcher) SearchBase(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
return ms.ds.SearchBase(req)
|
||||
}
|
||||
|
||||
func (ms *MemorySearcher) SearchSubschema(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
return ms.ds.SearchSubschema(req)
|
||||
}
|
||||
|
||||
func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) {
|
||||
accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access")
|
||||
baseDN := ms.si.GetBaseDN()
|
||||
|
||||
filterOC, err := ldap.GetFilterObjectClass(req.Filter)
|
||||
if err != nil {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": ms.si.GetOutpostName(),
|
||||
"type": "search",
|
||||
"reason": "filter_parse_fail",
|
||||
"app": ms.si.GetAppSlug(),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter)
|
||||
}
|
||||
if len(req.BindDN) < 1 {
|
||||
metrics.RequestsRejected.With(prometheus.Labels{
|
||||
"outpost_name": ms.si.GetOutpostName(),
|
||||
|
@ -88,11 +89,15 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
entries := make([]*ldap.Entry, 0)
|
||||
|
||||
scope := req.SearchRequest.Scope
|
||||
needUsers, needGroups := ms.si.GetNeededObjects(scope, req.BaseDN, filterOC)
|
||||
needUsers, needGroups := ms.si.GetNeededObjects(scope, req.BaseDN, req.FilterObjectClass)
|
||||
|
||||
if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) {
|
||||
if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) {
|
||||
entries = append(entries, ms.si.GetBaseEntry())
|
||||
if utils.IncludeObjectClass(req.FilterObjectClass, constants.GetDomainOCs()) {
|
||||
rootEntries, _ := ms.SearchBase(req)
|
||||
for _, e := range rootEntries.Entries {
|
||||
e.DN = ms.si.GetBaseDN()
|
||||
entries = append(entries, e)
|
||||
}
|
||||
}
|
||||
|
||||
scope -= 1 // Bring it from WholeSubtree to SingleLevel and so on
|
||||
|
@ -100,6 +105,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
|
||||
var users *[]api.User
|
||||
var groups []*group.LDAPGroup
|
||||
var err error
|
||||
|
||||
if needUsers {
|
||||
if flags.CanSearch {
|
||||
|
@ -159,12 +165,12 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseUserDN())) {
|
||||
singleu := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseUserDN())
|
||||
|
||||
if !singleu && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseUserDN(), constants.OUUsers))
|
||||
if !singleu && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(req.FilterObjectClass, ms.si.GetBaseUserDN(), constants.OUUsers))
|
||||
scope -= 1
|
||||
}
|
||||
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) {
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetUserOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := ms.si.UserEntry(u)
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singleu {
|
||||
|
@ -179,12 +185,12 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseGroupDN())) {
|
||||
singleg := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseGroupDN())
|
||||
|
||||
if !singleg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseGroupDN(), constants.OUGroups))
|
||||
if !singleg && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(req.FilterObjectClass, ms.si.GetBaseGroupDN(), constants.OUGroups))
|
||||
scope -= 1
|
||||
}
|
||||
|
||||
if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) {
|
||||
if scope >= 0 && groups != nil && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetGroupOCs()) {
|
||||
for _, g := range groups {
|
||||
if strings.EqualFold(req.BaseDN, g.DN) || !singleg {
|
||||
entries = append(entries, g.Entry())
|
||||
|
@ -198,12 +204,12 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
|||
if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseVirtualGroupDN())) {
|
||||
singlevg := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseVirtualGroupDN())
|
||||
|
||||
if !singlevg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups))
|
||||
if !singlevg && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetContainerOCs()) {
|
||||
entries = append(entries, utils.GetContainerEntry(req.FilterObjectClass, ms.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups))
|
||||
scope -= 1
|
||||
}
|
||||
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) {
|
||||
if scope >= 0 && users != nil && utils.IncludeObjectClass(req.FilterObjectClass, constants.GetVirtualGroupOCs()) {
|
||||
for _, u := range *users {
|
||||
entry := group.FromAPIUser(u, ms.si).Entry()
|
||||
if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg {
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
type Request struct {
|
||||
ldap.SearchRequest
|
||||
BindDN string
|
||||
FilterObjectClass string
|
||||
log *log.Entry
|
||||
|
||||
id string
|
||||
|
@ -40,11 +41,24 @@ func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Re
|
|||
})
|
||||
span.SetTag("ldap_filter", searchReq.Filter)
|
||||
span.SetTag("ldap_base_dn", searchReq.BaseDN)
|
||||
l := log.WithFields(log.Fields{
|
||||
"bindDN": bindDN,
|
||||
"baseDN": searchReq.BaseDN,
|
||||
"requestId": rid,
|
||||
"scope": ldap.ScopeMap[searchReq.Scope],
|
||||
"client": utils.GetIP(conn.RemoteAddr()),
|
||||
"filter": searchReq.Filter,
|
||||
})
|
||||
filterOC, err := ldap.GetFilterObjectClass(searchReq.Filter)
|
||||
if err != nil && len(searchReq.Filter) > 0 {
|
||||
l.WithError(err).WithField("objectClass", filterOC).Warning("invalid filter object class")
|
||||
}
|
||||
return &Request{
|
||||
SearchRequest: searchReq,
|
||||
BindDN: bindDN,
|
||||
FilterObjectClass: filterOC,
|
||||
conn: conn,
|
||||
log: log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("scope", ldap.ScopeMap[searchReq.Scope]).WithField("client", utils.GetIP(conn.RemoteAddr())).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
|
||||
log: l,
|
||||
id: rid,
|
||||
ctx: span.Context(),
|
||||
}, span
|
||||
|
@ -61,3 +75,19 @@ func (r *Request) Log() *log.Entry {
|
|||
func (r *Request) RemoteAddr() string {
|
||||
return utils.GetIP(r.conn.RemoteAddr())
|
||||
}
|
||||
|
||||
func (r *Request) FilterLDAPAttributes(res ldap.ServerSearchResult, cb func(attr *ldap.EntryAttribute) bool) ldap.ServerSearchResult {
|
||||
for _, e := range res.Entries {
|
||||
newAttrs := []*ldap.EntryAttribute{}
|
||||
for _, attr := range e.Attributes {
|
||||
include := cb(attr)
|
||||
if include {
|
||||
newAttrs = append(newAttrs, attr)
|
||||
} else {
|
||||
r.Log().WithField("key", attr.Name).Trace("filtering out field based on LDAP request")
|
||||
}
|
||||
}
|
||||
e.Attributes = newAttrs
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -6,4 +6,6 @@ import (
|
|||
|
||||
type Searcher interface {
|
||||
Search(req *Request) (ldap.ServerSearchResult, error)
|
||||
SearchBase(req *Request) (ldap.ServerSearchResult, error)
|
||||
SearchSubschema(req *Request) (ldap.ServerSearchResult, error)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"beryju.io/ldap"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"goauthentik.io/internal/outpost/ldap/constants"
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
)
|
||||
|
||||
func (ls *LDAPServer) providerForRequest(req *search.Request) *ProviderInstance {
|
||||
parsedBaseDN, err := goldap.ParseDN(strings.ToLower(req.BaseDN))
|
||||
if err != nil {
|
||||
req.Log().WithError(err).Info("failed to parse base DN")
|
||||
return nil
|
||||
}
|
||||
parsedBindDN, err := goldap.ParseDN(strings.ToLower(req.BindDN))
|
||||
if err != nil {
|
||||
req.Log().WithError(err).Info("failed to parse bind DN")
|
||||
return nil
|
||||
}
|
||||
var selectedProvider *ProviderInstance
|
||||
longestMatch := 0
|
||||
for _, provider := range ls.providers {
|
||||
providerBase, _ := goldap.ParseDN(strings.ToLower(provider.BaseDN))
|
||||
// Try to match the provider primarily based on the search request's base DN
|
||||
baseDNMatches := providerBase.AncestorOf(parsedBaseDN) || providerBase.Equal(parsedBaseDN)
|
||||
// But also try to match the provider based on the bind DN
|
||||
bindDNMatches := providerBase.AncestorOf(parsedBindDN) || providerBase.Equal(parsedBindDN)
|
||||
if baseDNMatches || bindDNMatches {
|
||||
// Only select the provider if it's a more precise match than previously
|
||||
if len(provider.BaseDN) > longestMatch {
|
||||
req.Log().WithField("provider", provider.BaseDN).Trace("selecting provider for search request")
|
||||
selectedProvider = provider
|
||||
longestMatch = len(provider.BaseDN)
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectedProvider
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) searchRoute(req *search.Request, pi *ProviderInstance) (ldap.ServerSearchResult, error) {
|
||||
// Route based on the base DN
|
||||
if len(req.BaseDN) == 0 {
|
||||
req.Log().Trace("routing to base")
|
||||
return pi.searcher.SearchBase(req)
|
||||
}
|
||||
if strings.EqualFold(req.BaseDN, "cn=subschema") || req.FilterObjectClass == constants.OCSubSchema {
|
||||
req.Log().Trace("routing to subschema")
|
||||
return pi.searcher.SearchSubschema(req)
|
||||
}
|
||||
req.Log().Trace("routing to default")
|
||||
return pi.searcher.Search(req)
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) filterResultAttributes(req *search.Request, result ldap.ServerSearchResult) ldap.ServerSearchResult {
|
||||
allowedAttributes := []string{}
|
||||
if len(req.Attributes) == 1 && req.Attributes[0] == constants.SearchAttributeNone {
|
||||
allowedAttributes = []string{"objectClass"}
|
||||
}
|
||||
if len(req.Attributes) > 0 {
|
||||
// Only strictly filter allowed attributes if we haven't already narrowed the attributes
|
||||
// down
|
||||
if len(allowedAttributes) < 1 {
|
||||
allowedAttributes = req.Attributes
|
||||
}
|
||||
// Filter LDAP returned attributes by search requested attributes, taking "1.1"
|
||||
// into consideration
|
||||
return req.FilterLDAPAttributes(result, func(attr *ldap.EntryAttribute) bool {
|
||||
for _, allowed := range allowedAttributes {
|
||||
if allowed == constants.SearchAttributeAllUser ||
|
||||
allowed == constants.SearchAttributeAllOperational {
|
||||
return true
|
||||
}
|
||||
if strings.EqualFold(allowed, attr.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -35,6 +35,5 @@ type LDAPServerInstance interface {
|
|||
GetFlags(dn string) *flags.UserFlags
|
||||
SetFlags(dn string, flags *flags.UserFlags)
|
||||
|
||||
GetBaseEntry() *ldap.Entry
|
||||
GetNeededObjects(int, string, string) (bool, bool)
|
||||
GetNeededObjects(scope int, baseDN string, filterOC string) (bool, bool)
|
||||
}
|
||||
|
|
|
@ -238,88 +238,82 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
{
|
||||
"dn": f"cn={o_user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
|
||||
"attributes": {
|
||||
"cn": [o_user.username],
|
||||
"sAMAccountName": [o_user.username],
|
||||
"uid": [o_user.uid],
|
||||
"name": [o_user.name],
|
||||
"displayName": [o_user.name],
|
||||
"sn": [o_user.name],
|
||||
"mail": [""],
|
||||
"cn": o_user.username,
|
||||
"sAMAccountName": o_user.username,
|
||||
"uid": o_user.uid,
|
||||
"name": o_user.name,
|
||||
"displayName": o_user.name,
|
||||
"sn": o_user.name,
|
||||
"mail": "",
|
||||
"objectClass": [
|
||||
"user",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"goauthentik.io/ldap/user",
|
||||
],
|
||||
"uidNumber": [str(2000 + o_user.pk)],
|
||||
"gidNumber": [str(2000 + o_user.pk)],
|
||||
"uidNumber": 2000 + o_user.pk,
|
||||
"gidNumber": 2000 + o_user.pk,
|
||||
"memberOf": [],
|
||||
"homeDirectory": [
|
||||
f"/home/{o_user.username}",
|
||||
],
|
||||
"ak-active": ["true"],
|
||||
"ak-superuser": ["false"],
|
||||
"goauthentikio-user-override-ips": ["true"],
|
||||
"goauthentikio-user-service-account": ["true"],
|
||||
"homeDirectory": f"/home/{o_user.username}",
|
||||
"ak-active": True,
|
||||
"ak-superuser": False,
|
||||
"goauthentikio-user-override-ips": True,
|
||||
"goauthentikio-user-service-account": True,
|
||||
},
|
||||
"type": "searchResEntry",
|
||||
},
|
||||
{
|
||||
"dn": f"cn={embedded_account.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
|
||||
"attributes": {
|
||||
"cn": [embedded_account.username],
|
||||
"sAMAccountName": [embedded_account.username],
|
||||
"uid": [embedded_account.uid],
|
||||
"name": [embedded_account.name],
|
||||
"displayName": [embedded_account.name],
|
||||
"sn": [embedded_account.name],
|
||||
"mail": [""],
|
||||
"cn": embedded_account.username,
|
||||
"sAMAccountName": embedded_account.username,
|
||||
"uid": embedded_account.uid,
|
||||
"name": embedded_account.name,
|
||||
"displayName": embedded_account.name,
|
||||
"sn": embedded_account.name,
|
||||
"mail": "",
|
||||
"objectClass": [
|
||||
"user",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"goauthentik.io/ldap/user",
|
||||
],
|
||||
"uidNumber": [str(2000 + embedded_account.pk)],
|
||||
"gidNumber": [str(2000 + embedded_account.pk)],
|
||||
"uidNumber": 2000 + embedded_account.pk,
|
||||
"gidNumber": 2000 + embedded_account.pk,
|
||||
"memberOf": [],
|
||||
"homeDirectory": [
|
||||
f"/home/{embedded_account.username}",
|
||||
],
|
||||
"ak-active": ["true"],
|
||||
"ak-superuser": ["false"],
|
||||
"goauthentikio-user-override-ips": ["true"],
|
||||
"goauthentikio-user-service-account": ["true"],
|
||||
"homeDirectory": f"/home/{embedded_account.username}",
|
||||
"ak-active": True,
|
||||
"ak-superuser": False,
|
||||
"goauthentikio-user-override-ips": True,
|
||||
"goauthentikio-user-service-account": True,
|
||||
},
|
||||
"type": "searchResEntry",
|
||||
},
|
||||
{
|
||||
"dn": f"cn={self.user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
|
||||
"attributes": {
|
||||
"cn": [self.user.username],
|
||||
"sAMAccountName": [self.user.username],
|
||||
"uid": [self.user.uid],
|
||||
"name": [self.user.name],
|
||||
"displayName": [self.user.name],
|
||||
"sn": [self.user.name],
|
||||
"mail": [self.user.email],
|
||||
"cn": self.user.username,
|
||||
"sAMAccountName": self.user.username,
|
||||
"uid": self.user.uid,
|
||||
"name": self.user.name,
|
||||
"displayName": self.user.name,
|
||||
"sn": self.user.name,
|
||||
"mail": self.user.email,
|
||||
"objectClass": [
|
||||
"user",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"goauthentik.io/ldap/user",
|
||||
],
|
||||
"uidNumber": [str(2000 + self.user.pk)],
|
||||
"gidNumber": [str(2000 + self.user.pk)],
|
||||
"uidNumber": 2000 + self.user.pk,
|
||||
"gidNumber": 2000 + self.user.pk,
|
||||
"memberOf": [
|
||||
f"cn={group.name},ou=groups,dc=ldap,dc=goauthentik,dc=io"
|
||||
for group in self.user.ak_groups.all()
|
||||
],
|
||||
"homeDirectory": [
|
||||
f"/home/{self.user.username}",
|
||||
],
|
||||
"ak-active": ["true"],
|
||||
"ak-superuser": ["true"],
|
||||
"homeDirectory": f"/home/{self.user.username}",
|
||||
"ak-active": True,
|
||||
"ak-superuser": True,
|
||||
"extraAttribute": ["bar"],
|
||||
},
|
||||
"type": "searchResEntry",
|
||||
|
|
Reference in New Issue