Merge branch 'main' into 5165-password-strength-indicator
* main: providers/ldap: rework Schema and DSE (#5838) web/flows: update default flow background (#5905) web: bump @formatjs/intl-listformat from 7.2.2 to 7.3.0 in /web (#5866) website/integrations: add account linking note for WriteFreely (#5804) web: bump @storybook/addon-essentials from 7.0.18 to 7.0.20 in /web (#5894) web: bump @storybook/web-components-vite from 7.0.18 to 7.0.20 in /web (#5895) web: bump @storybook/blocks from 7.0.18 to 7.0.20 in /web (#5893) web: bump storybook from 7.0.18 to 7.0.20 in /web (#5896) website/docs: correct LDAP StartTLS documentation (#5886) core: bump python from 3.11.3-slim-bullseye to 3.11.4-slim-bullseye (#5891) ci: bump docker/setup-qemu-action from 2.1.0 to 2.2.0 (#5892) core: bump selenium from 4.9.1 to 4.10.0 (#5897) web: bump pyright from 1.1.312 to 1.1.313 in /web (#5898) web: bump @storybook/addon-links from 7.0.18 to 7.0.20 in /web (#5899) web: bump @storybook/web-components from 7.0.18 to 7.0.20 in /web (#5900) core: bump urllib3 from 2.0.2 to 2.0.3 (#5901) core: bump ruff from 0.0.271 to 0.0.272 (#5902) core: bump sentry-sdk from 1.25.0 to 1.25.1 (#5903)
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -190,7 +190,7 @@ jobs: | ||||
|         with: | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2.1.0 | ||||
|         uses: docker/setup-qemu-action@v2.2.0 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|       - name: prepare variables | ||||
| @ -234,7 +234,7 @@ jobs: | ||||
|         with: | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2.1.0 | ||||
|         uses: docker/setup-qemu-action@v2.2.0 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|       - name: prepare variables | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							| @ -68,7 +68,7 @@ jobs: | ||||
|         with: | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2.1.0 | ||||
|         uses: docker/setup-qemu-action@v2.2.0 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|       - name: prepare variables | ||||
|  | ||||
							
								
								
									
										4
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -10,7 +10,7 @@ jobs: | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2.1.0 | ||||
|         uses: docker/setup-qemu-action@v2.2.0 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|       - name: prepare variables | ||||
| @ -59,7 +59,7 @@ jobs: | ||||
|         with: | ||||
|           go-version-file: "go.mod" | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2.1.0 | ||||
|         uses: docker/setup-qemu-action@v2.2.0 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|       - name: prepare variables | ||||
|  | ||||
| @ -20,7 +20,7 @@ WORKDIR /work/web | ||||
| RUN npm ci --include=dev && npm run build | ||||
|  | ||||
| # Stage 3: Poetry to requirements.txt export | ||||
| FROM docker.io/python:3.11.3-slim-bullseye AS poetry-locker | ||||
| FROM docker.io/python:3.11.4-slim-bullseye AS poetry-locker | ||||
|  | ||||
| WORKDIR /work | ||||
| COPY ./pyproject.toml /work | ||||
| @ -63,7 +63,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ | ||||
|     " | ||||
|  | ||||
| # Stage 6: Run | ||||
| FROM docker.io/python:3.11.3-slim-bullseye AS final-image | ||||
| FROM docker.io/python:3.11.4-slim-bullseye AS final-image | ||||
|  | ||||
| LABEL org.opencontainers.image.url https://goauthentik.io | ||||
| LABEL org.opencontainers.image.description goauthentik.io Main server image, see https://goauthentik.io for more info. | ||||
|  | ||||
| @ -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 ( | ||||
| @ -20,7 +29,7 @@ const ( | ||||
| 	OCOrgPerson     = "organizationalPerson" | ||||
| 	OCInetOrgPerson = "inetOrgPerson" | ||||
| 	OCAKUser        = "goauthentik.io/ldap/user" | ||||
| 	OCPosixAccount        = "posixAccount" | ||||
| 	OCPosixAccount  = "posixAccount" | ||||
| ) | ||||
|  | ||||
| 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 { | ||||
|  | ||||
							
								
								
									
										96
									
								
								internal/outpost/ldap/search/direct/schema.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								internal/outpost/ldap/search/direct/schema.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 { | ||||
|  | ||||
| @ -15,8 +15,9 @@ import ( | ||||
|  | ||||
| type Request struct { | ||||
| 	ldap.SearchRequest | ||||
| 	BindDN string | ||||
| 	log    *log.Entry | ||||
| 	BindDN            string | ||||
| 	FilterObjectClass string | ||||
| 	log               *log.Entry | ||||
|  | ||||
| 	id   string | ||||
| 	conn net.Conn | ||||
| @ -40,13 +41,26 @@ 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, | ||||
| 		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), | ||||
| 		id:            rid, | ||||
| 		ctx:           span.Context(), | ||||
| 		SearchRequest:     searchReq, | ||||
| 		BindDN:            bindDN, | ||||
| 		FilterObjectClass: filterOC, | ||||
| 		conn:              conn, | ||||
| 		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) | ||||
| } | ||||
|  | ||||
							
								
								
									
										84
									
								
								internal/outpost/ldap/search_route.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								internal/outpost/ldap/search_route.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| } | ||||
|  | ||||
							
								
								
									
										54
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -3114,41 +3114,41 @@ pyasn1 = ">=0.1.3" | ||||
|  | ||||
| [[package]] | ||||
| name = "ruff" | ||||
| version = "0.0.271" | ||||
| version = "0.0.272" | ||||
| description = "An extremely fast Python linter, written in Rust." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "ruff-0.0.271-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:1a627978df924635f7d1a169a98abb2ea488c2d409da56a3f9e44a82d30606ac"}, | ||||
|     {file = "ruff-0.0.271-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:f47d8a192f6869e95896dc5bb7e825a4f9c554136b9c3bddd38389e43d4db08b"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e5de841e09ea75a26956a2cda930d1260c9d8d94cbe57c13b3e881d96526860"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:191cdddfc82165afd63ab29ad671419a90a5e699b026ac2d9c61232543965de6"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e34ca86329a542ab5d31f4fc2702f556d62748f4217e2f6951aef93176190f0"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7543b8a32e000ed30727ca6e570a90ab26f8899ee23dffb28806dfc2618782fb"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fca503741f4b23a7179fd7a9bc50fc2cca637e9a4da027776f38690c50ae559f"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f445c56cdc8c12fc28a0b16588ba33abebb6340cb5b1b5a7d5668d4c0b31ad33"}, | ||||
|     {file = "ruff-0.0.271-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a73ffda5548ea8e28e0afcfa698a8675bb17f7048299327f4c1a1287b6e36a2"}, | ||||
|     {file = "ruff-0.0.271-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67525aa821ff0f8371eaa28c73dc467b8eea18931a8bd749775ad538fe1f35e6"}, | ||||
|     {file = "ruff-0.0.271-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f3fd9e7c7afb7740d2734af3348e6c88226b42acba2e10a3d1e449caa67e4652"}, | ||||
|     {file = "ruff-0.0.271-py3-none-musllinux_1_2_i686.whl", hash = "sha256:efdfe7fea656eb2ed54f123135c04f71744ad6e4c0c6be156d46e7a2f4730d48"}, | ||||
|     {file = "ruff-0.0.271-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cd43c1aff3eefb2193a125a12124438f65a8d1a6da0e86f8545141d48f6a39fa"}, | ||||
|     {file = "ruff-0.0.271-py3-none-win32.whl", hash = "sha256:403e8f9de18b2279d65015a45e0e0d98d60ad878d52f46904f502a4d09465815"}, | ||||
|     {file = "ruff-0.0.271-py3-none-win_amd64.whl", hash = "sha256:140e912a18a662062b04b489861e5aebdbe1a1668bf416e5a951f2347aa65907"}, | ||||
|     {file = "ruff-0.0.271-py3-none-win_arm64.whl", hash = "sha256:45b3c3551a798d9786779c6dd7ad2224af6e06162e17f4a0e7678d3e9115ae56"}, | ||||
|     {file = "ruff-0.0.271.tar.gz", hash = "sha256:be4590137a31c47e7f6ef4488d60102c68102f842453355d8073193a30199aa7"}, | ||||
|     {file = "ruff-0.0.272-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ae9b57546e118660175d45d264b87e9b4c19405c75b587b6e4d21e6a17bf4fdf"}, | ||||
|     {file = "ruff-0.0.272-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:1609b864a8d7ee75a8c07578bdea0a7db75a144404e75ef3162e0042bfdc100d"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee76b4f05fcfff37bd6ac209d1370520d509ea70b5a637bdf0a04d0c99e13dff"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48eccf225615e106341a641f826b15224b8a4240b84269ead62f0afd6d7e2d95"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:677284430ac539bb23421a2b431b4ebc588097ef3ef918d0e0a8d8ed31fea216"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9c4bfb75456a8e1efe14c52fcefb89cfb8f2a0d31ed8d804b82c6cf2dc29c42c"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86bc788245361a8148ff98667da938a01e1606b28a45e50ac977b09d3ad2c538"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b2ea68d2aa69fff1b20b67636b1e3e22a6a39e476c880da1282c3e4bf6ee5a"}, | ||||
|     {file = "ruff-0.0.272-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd2bbe337a3f84958f796c77820d55ac2db1e6753f39d1d1baed44e07f13f96d"}, | ||||
|     {file = "ruff-0.0.272-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d5a208f8ef0e51d4746930589f54f9f92f84bb69a7d15b1de34ce80a7681bc00"}, | ||||
|     {file = "ruff-0.0.272-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:905ff8f3d6206ad56fcd70674453527b9011c8b0dc73ead27618426feff6908e"}, | ||||
|     {file = "ruff-0.0.272-py3-none-musllinux_1_2_i686.whl", hash = "sha256:19643d448f76b1eb8a764719072e9c885968971bfba872e14e7257e08bc2f2b7"}, | ||||
|     {file = "ruff-0.0.272-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:691d72a00a99707a4e0b2846690961157aef7b17b6b884f6b4420a9f25cd39b5"}, | ||||
|     {file = "ruff-0.0.272-py3-none-win32.whl", hash = "sha256:dc406e5d756d932da95f3af082814d2467943631a587339ee65e5a4f4fbe83eb"}, | ||||
|     {file = "ruff-0.0.272-py3-none-win_amd64.whl", hash = "sha256:a37ec80e238ead2969b746d7d1b6b0d31aa799498e9ba4281ab505b93e1f4b28"}, | ||||
|     {file = "ruff-0.0.272-py3-none-win_arm64.whl", hash = "sha256:06b8ee4eb8711ab119db51028dd9f5384b44728c23586424fd6e241a5b9c4a3b"}, | ||||
|     {file = "ruff-0.0.272.tar.gz", hash = "sha256:273a01dc8c3c4fd4c2af7ea7a67c8d39bb09bce466e640dd170034da75d14cab"}, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "selenium" | ||||
| version = "4.9.1" | ||||
| version = "4.10.0" | ||||
| description = "" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "selenium-4.9.1-py3-none-any.whl", hash = "sha256:82aedaa85d55bc861f4c89ff9609e82f6c958e2e1e3da3ffcc36703f21d3ee16"}, | ||||
|     {file = "selenium-4.9.1.tar.gz", hash = "sha256:3444f4376321530c36ce8355b6b357d8cf4a7d588ce5cf772183465930bbed0e"}, | ||||
|     {file = "selenium-4.10.0-py3-none-any.whl", hash = "sha256:40241b9d872f58959e9b34e258488bf11844cd86142fd68182bd41db9991fc5c"}, | ||||
|     {file = "selenium-4.10.0.tar.gz", hash = "sha256:871bf800c4934f745b909c8dfc7d15c65cf45bd2e943abd54451c810ada395e3"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3159,14 +3159,14 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} | ||||
|  | ||||
| [[package]] | ||||
| name = "sentry-sdk" | ||||
| version = "1.25.0" | ||||
| version = "1.25.1" | ||||
| description = "Python client for Sentry (https://sentry.io)" | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "sentry-sdk-1.25.0.tar.gz", hash = "sha256:5be3296fc574fa8a4d9b213b4dcf8c8d0246c08f8bd78315c6286f386c37555a"}, | ||||
|     {file = "sentry_sdk-1.25.0-py2.py3-none-any.whl", hash = "sha256:fe85cf5d0b3d0aa3480df689f9f6dc487de783defb0a95043368375dc893645e"}, | ||||
|     {file = "sentry-sdk-1.25.1.tar.gz", hash = "sha256:aa796423eb6a2f4a8cd7a5b02ba6558cb10aab4ccdc0537f63a47b038c520c38"}, | ||||
|     {file = "sentry_sdk-1.25.1-py2.py3-none-any.whl", hash = "sha256:79afb7c896014038e358401ad1d36889f97a129dfa8031c49b3f238cd1aa3935"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3590,14 +3590,14 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "urllib3" | ||||
| version = "2.0.2" | ||||
| version = "2.0.3" | ||||
| description = "HTTP library with thread-safe connection pooling, file post, and more." | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, | ||||
|     {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, | ||||
|     {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, | ||||
|     {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
| @ -7,3 +7,4 @@ coverage | ||||
| # Import order matters | ||||
| poly.ts | ||||
| src/locale-codes.ts | ||||
| src/locales/ | ||||
|  | ||||
							
								
								
									
										9
									
								
								web/.storybook/authentikTheme.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/.storybook/authentikTheme.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| import { create } from "@storybook/theming/create"; | ||||
|  | ||||
| export default create({ | ||||
|     base: "light", | ||||
|     brandTitle: "authentik Storybook", | ||||
|     brandUrl: "https://goauthentik.io", | ||||
|     brandImage: "https://goauthentik.io/img/icon_left_brand_colour.svg", | ||||
|     brandTarget: "_self", | ||||
| }); | ||||
							
								
								
									
										8
									
								
								web/.storybook/manager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								web/.storybook/manager.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| // .storybook/manager.js | ||||
| import { addons } from "@storybook/manager-api"; | ||||
|  | ||||
| import authentikTheme from "./authentikTheme"; | ||||
|  | ||||
| addons.setConfig({ | ||||
|     theme: authentikTheme, | ||||
| }); | ||||
							
								
								
									
										5803
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5803
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -32,7 +32,7 @@ | ||||
|         "@codemirror/lang-xml": "^6.0.2", | ||||
|         "@codemirror/legacy-modes": "^6.3.2", | ||||
|         "@codemirror/theme-one-dark": "^6.1.2", | ||||
|         "@formatjs/intl-listformat": "^7.2.2", | ||||
|         "@formatjs/intl-listformat": "^7.3.0", | ||||
|         "@fortawesome/fontawesome-free": "^6.4.0", | ||||
|         "@goauthentik/api": "^2023.5.3-1685646044", | ||||
|         "@lit/localize": "^0.11.4", | ||||
| @ -73,11 +73,11 @@ | ||||
|         "@rollup/plugin-replace": "^5.0.2", | ||||
|         "@rollup/plugin-typescript": "^11.1.1", | ||||
|         "@squoosh/cli": "^0.7.3", | ||||
|         "@storybook/addon-essentials": "^7.0.18", | ||||
|         "@storybook/addon-links": "^7.0.18", | ||||
|         "@storybook/blocks": "^7.0.18", | ||||
|         "@storybook/web-components": "^7.0.18", | ||||
|         "@storybook/web-components-vite": "^7.0.18", | ||||
|         "@storybook/addon-essentials": "^7.0.20", | ||||
|         "@storybook/addon-links": "^7.0.20", | ||||
|         "@storybook/blocks": "^7.0.20", | ||||
|         "@storybook/web-components": "^7.0.20", | ||||
|         "@storybook/web-components-vite": "^7.0.20", | ||||
|         "@trivago/prettier-plugin-sort-imports": "^4.1.1", | ||||
|         "@types/chart.js": "^2.9.37", | ||||
|         "@types/codemirror": "5.60.8", | ||||
| @ -95,7 +95,7 @@ | ||||
|         "lit-analyzer": "^1.2.1", | ||||
|         "npm-run-all": "^4.1.5", | ||||
|         "prettier": "^2.8.8", | ||||
|         "pyright": "^1.1.312", | ||||
|         "pyright": "^1.1.313", | ||||
|         "react": "^18.2.0", | ||||
|         "react-dom": "^18.2.0", | ||||
|         "rollup": "^2.79.1", | ||||
| @ -104,7 +104,7 @@ | ||||
|         "rollup-plugin-minify-html-literals": "^1.2.6", | ||||
|         "rollup-plugin-postcss-lit": "^2.0.0", | ||||
|         "rollup-plugin-terser": "^7.0.2", | ||||
|         "storybook": "^7.0.18", | ||||
|         "storybook": "^7.0.20", | ||||
|         "ts-lit-plugin": "^1.2.1", | ||||
|         "tslib": "^2.5.3", | ||||
|         "turnstile-types": "^1.1.2", | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 595 KiB | 
| @ -525,7 +525,8 @@ export class FlowExecutor extends Interface implements StageHost { | ||||
|                                                 ${this.flowInfo?.background?.startsWith("/static") | ||||
|                                                     ? html` | ||||
|                                                           <li> | ||||
|                                                               <a href="https://unsplash.com/@joshnh" | ||||
|                                                               <a | ||||
|                                                                   href="https://unsplash.com/@diegojimenez" | ||||
|                                                                   >${msg("Background image")}</a | ||||
|                                                               > | ||||
|                                                           </li> | ||||
|  | ||||
| @ -3,5 +3,5 @@ import "@webcomponents/webcomponentsjs"; | ||||
| import "lit/polyfill-support.js"; | ||||
| import "core-js/actual"; | ||||
|  | ||||
| import "@formatjs/intl-listformat/polyfill.js"; | ||||
| import "@formatjs/intl-listformat/locale-data/en.js"; | ||||
| import "@formatjs/intl-listformat/polyfill"; | ||||
| import "@formatjs/intl-listformat/locale-data/en"; | ||||
|  | ||||
| @ -60,14 +60,13 @@ Starting with 2023.3, periods and slashes in custom attributes will be sanitized | ||||
|  | ||||
| You can also configure SSL for your LDAP Providers by selecting a certificate and a server name in the provider settings. | ||||
|  | ||||
| Starting with authentik 2023.6, StartTLS is supported, and the provider will pick the correct certificate based on the DN a bind attempt is made with. | ||||
| Starting with authentik 2023.6, StartTLS is supported, and the provider will pick the correct certificate based on the configured _TLS Server name_ field. The certificate is not picked based on the Bind DN, as the StartTLS operation should happen be the bind request to ensure bind credentials are transmitted over TLS. | ||||
|  | ||||
| This enables you to bind on port 636 using LDAPS. | ||||
|  | ||||
| ## Integrations | ||||
|  | ||||
| See the integration guide for [sssd](../../../integrations/services/sssd/) for | ||||
| an example guide. | ||||
| See the integration guide for [sssd](../../../integrations/services/sssd/) for an example guide. | ||||
|  | ||||
| ## Bind Modes | ||||
|  | ||||
|  | ||||
| @ -28,7 +28,7 @@ The following placeholders will be used: | ||||
|  | ||||
| Create a OAuth2/OpenID Provider (under _Applications/Providers_) with these settings: | ||||
|  | ||||
| -   Name : writefreely | ||||
| -   Name: writefreely | ||||
| -   Redirect URI: `https://writefreely.company/oauth/callback/generic` | ||||
|  | ||||
| ### Step 3 - Application | ||||
| @ -88,6 +88,12 @@ map_email          = email | ||||
|  | ||||
| Restart writefreely.service | ||||
|  | ||||
| ## Account linking | ||||
|  | ||||
| If your usernames in authentik and WriteFreely are different, you might need to link your accounts before being able to use SSO. | ||||
|  | ||||
| To link the accounts, first log into Writefreely with local credentials, and then navigate to **Customize -->Account Settings**. In the option "Linked Accounts", click on "authentik". | ||||
|  | ||||
| ## Additional Resources | ||||
|  | ||||
| -   https://writefreely.org/docs/latest/admin/config | ||||
|  | ||||
							
								
								
									
										1
									
								
								website/static/img/icon_left_brand_colour.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								website/static/img/icon_left_brand_colour.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 994.71 151.65"><defs><style>.cls-1{fill:#fd4b2d;}</style></defs><path class="cls-1" d="M284.72,50.4H305.5v82.84H284.72v-8.76a40.79,40.79,0,0,1-12.21,8.34,34.14,34.14,0,0,1-13.27,2.55q-16.05,0-27.76-12.45T219.77,92q0-19.18,11.33-31.45t27.53-12.26a34.94,34.94,0,0,1,14,2.82,38.32,38.32,0,0,1,12.1,8.45ZM262.87,67.45a21,21,0,0,0-16,6.82q-6.37,6.81-6.38,17.47T247,109.4a21,21,0,0,0,16,6.93,21.42,21.42,0,0,0,16.24-6.81q6.45-6.81,6.45-17.86,0-10.8-6.45-17.51A21.71,21.71,0,0,0,262.87,67.45Z"/><path class="cls-1" d="M335.8,50.4h21V90.29q0,11.65,1.6,16.18a14.16,14.16,0,0,0,5.16,7,14.76,14.76,0,0,0,8.74,2.51,15.25,15.25,0,0,0,8.81-2.48,14.49,14.49,0,0,0,5.38-7.27q1.31-3.57,1.3-15.3V50.4h20.79V85.5q0,21.69-3.43,29.69a32.32,32.32,0,0,1-12.33,15q-8.16,5.22-20.71,5.22-13.64,0-22.05-6.09a32.2,32.2,0,0,1-11.84-17q-2.43-7.55-2.43-27.41Z"/><path class="cls-1" d="M441.32,19.86H462.1V50.4h12.34V68.29H462.1v65H441.32V68.29H430.66V50.4h10.66Z"/><path class="cls-1" d="M495,18.42h20.63V58.77a47.41,47.41,0,0,1,12.26-7.88,31.62,31.62,0,0,1,12.49-2.63,28.13,28.13,0,0,1,20.78,8.53q7.23,7.4,7.24,21.7v54.75H547.9V96.92q0-14.4-1.37-19.49a13.6,13.6,0,0,0-4.68-7.62,13.19,13.19,0,0,0-8.18-2.51,15.43,15.43,0,0,0-10.85,4.19,22.14,22.14,0,0,0-6.28,11.42q-.91,3.72-.92,17v33.28H495Z"/><path class="cls-1" d="M680.84,97.83H614.06a22.25,22.25,0,0,0,7.73,14q6.29,5.22,16,5.21a27.7,27.7,0,0,0,20-8.14l17.51,8.22a41.31,41.31,0,0,1-15.68,13.74q-9.13,4.46-21.7,4.46-19.5,0-31.75-12.3T594,92.27q0-19,12.22-31.48t30.65-12.53q19.56,0,31.82,12.53t12.26,33.08ZM660.05,81.46a20.87,20.87,0,0,0-8.12-11.27,23.61,23.61,0,0,0-14.08-4.34,24.88,24.88,0,0,0-15.25,4.88q-4.11,3-7.62,10.73Z"/><path class="cls-1" d="M707,50.4H727.8v8.49a50.15,50.15,0,0,1,12.81-8.3,31.08,31.08,0,0,1,11.75-2.33,28.44,28.44,0,0,1,20.91,8.61q7.22,7.31,7.22,21.62v54.75H759.93V97q0-14.83-1.33-19.7A13.48,13.48,0,0,0,754,69.85a13,13,0,0,0-8.16-2.55A15.32,15.32,0,0,0,735,71.52a22.6,22.6,0,0,0-6.27,11.67q-.9,3.89-.91,16.81v33.24H707Z"/><path class="cls-1" d="M812.46,19.86h20.79V50.4h12.33V68.29H833.25v65H812.46V68.29H801.8V50.4h10.66Z"/><path class="cls-1" d="M874.16,16.29a12.74,12.74,0,0,1,9.38,3.95,13.18,13.18,0,0,1,3.91,9.6,13,13,0,0,1-3.87,9.48,12.6,12.6,0,0,1-9.27,3.92,12.73,12.73,0,0,1-9.45-4A13.39,13.39,0,0,1,861,29.53a12.78,12.78,0,0,1,3.87-9.36A12.71,12.71,0,0,1,874.16,16.29Z"/><rect class="cls-1" x="863.77" y="50.4" width="20.79" height="82.84"/><path class="cls-1" d="M913,18.42h20.78V84.55L964.34,50.4h26.11L954.76,90.1l40,43.14h-25.8L933.73,95.06v38.18H913Z"/><rect class="cls-1" x="107.1" y="34.93" width="6.37" height="18.2"/><rect class="cls-1" x="123.67" y="34.16" width="6.37" height="14.23"/><path class="cls-1" d="M30.83,55A23.23,23.23,0,0,0,10.41,67.13h10.8C26,63,32.94,61.8,38,67.13H49.39C44.93,61.09,38.24,55,30.83,55Z"/><path class="cls-1" d="M46.25,78.11c-14.89,31.15-41,4.6-25-11H10.41c-8.47,14.76,3.24,34.68,20.42,34.23,13.28,0,24.24-19.72,24.24-23.21,0-1.54-2.14-6.25-5.68-11H38A40.52,40.52,0,0,1,46.25,78.11Zm.4-.91Z"/><path class="cls-1" d="M189.62,34.71V117A28.62,28.62,0,0,1,161,145.54H148.89v-28H90.94v28H78.81A28.62,28.62,0,0,1,50.22,117V91.08h91.87V41.62H97.74V69.41H50.22V34.71a27.43,27.43,0,0,1,.19-3.29,27.09,27.09,0,0,1,.71-3.84c.1-.41.22-.82.34-1.21a2.13,2.13,0,0,1,.09-.3c.07-.21.13-.4.2-.59s.14-.4.21-.59.16-.44.25-.65.18-.43.26-.64a29.35,29.35,0,0,1,2.6-4.82l0-.05c.26-.37.53-.75.81-1.12s.47-.61.7-.91.57-.67.86-1,.56-.63.86-.93l0,0a4.53,4.53,0,0,1,.49-.49,29.23,29.23,0,0,1,3.4-2.84c.32-.24.66-.46,1-.68s.77-.49,1.17-.72a23.78,23.78,0,0,1,2.29-1.21l.75-.34a27.84,27.84,0,0,1,3.35-1.21c.44-.13.88-.24,1.33-.35a6.19,6.19,0,0,1,.65-.15,28.86,28.86,0,0,1,3.87-.57l.56,0h.28c.43,0,.87,0,1.31,0H161c.43,0,.87,0,1.3,0h.28l.56,0a29.25,29.25,0,0,1,3.88.57c.22,0,.43.09.65.15.45.11.88.22,1.32.35a27.23,27.23,0,0,1,3.35,1.21l.75.34a25.19,25.19,0,0,1,2.3,1.21c.39.23.78.47,1.16.72s.69.44,1,.68a29.23,29.23,0,0,1,3.91,3.36q.45.45.87.93c.29.32.57.66.85,1l.71.91c.28.37.54.75.8,1.12l0,.05a28.61,28.61,0,0,1,2.6,4.82l.27.64.24.65c.08.19.15.39.22.59l.19.59c0,.09.06.19.1.3.11.39.23.8.34,1.21a28.56,28.56,0,0,1,.7,3.84A27.42,27.42,0,0,1,189.62,34.71Z"/><path class="cls-1" d="M184.76,18.78H55.07A28.59,28.59,0,0,1,78.8,6.12H161A28.59,28.59,0,0,1,184.76,18.78Z"/><path class="cls-1" d="M189.43,31.43H50.4a28.29,28.29,0,0,1,4.67-12.65H184.76A28.17,28.17,0,0,1,189.43,31.43Z"/><path class="cls-1" d="M189.63,34.71v9.37H142.09V41.62H97.74v2.46H50.21V34.71a27.43,27.43,0,0,1,.19-3.29h139A27.42,27.42,0,0,1,189.63,34.71Z"/><rect class="cls-1" x="50.21" y="44.08" width="47.54" height="12.66"/><rect class="cls-1" x="142.09" y="44.08" width="47.54" height="12.66"/><rect class="cls-1" x="50.21" y="56.74" width="47.54" height="12.65"/><rect class="cls-1" x="142.09" y="56.74" width="47.54" height="12.65"/></svg> | ||||
| After Width: | Height: | Size: 4.7 KiB | 
		Reference in New Issue
	
	Block a user
	 Ken Sternberg
					Ken Sternberg