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