outposts/ldap: Fix more case sensitivity issues. (#2144)
This commit is contained in:
		| @ -16,6 +16,7 @@ import ( | ||||
| 	"goauthentik.io/internal/outpost/ldap/flags" | ||||
| 	"goauthentik.io/internal/outpost/ldap/metrics" | ||||
| 	"goauthentik.io/internal/outpost/ldap/server" | ||||
| 	"goauthentik.io/internal/outpost/ldap/utils" | ||||
| ) | ||||
|  | ||||
| const ContextUserKey = "ak_user" | ||||
| @ -35,7 +36,7 @@ func NewDirectBinder(si server.LDAPServerInstance) *DirectBinder { | ||||
| } | ||||
|  | ||||
| func (db *DirectBinder) GetUsername(dn string) (string, error) { | ||||
| 	if !strings.HasSuffix(strings.ToLower(dn), strings.ToLower(db.si.GetBaseDN())) { | ||||
| 	if !utils.HasSuffixNoCase(dn, db.si.GetBaseDN()) { | ||||
| 		return "", errors.New("invalid base DN") | ||||
| 	} | ||||
| 	dns, err := goldap.ParseDN(dn) | ||||
|  | ||||
| @ -140,26 +140,26 @@ func (pi *ProviderInstance) GetNeededObjects(scope int, baseDN string, filterOC | ||||
| 	// If our requested base DN doesn't match any of the container DNs, then | ||||
| 	// we're probably loading a user or group. If it does, then make sure our | ||||
| 	// scope will eventually take us to users or groups. | ||||
| 	if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.UserDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetUserOCs()) { | ||||
| 	if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.UserDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetUserOCs()) { | ||||
| 		if baseDN != pi.UserDN && baseDN != pi.BaseDN || | ||||
| 			baseDN == pi.BaseDN && scope > 1 || | ||||
| 			baseDN == pi.UserDN && scope > 0 { | ||||
| 			strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 || | ||||
| 			strings.EqualFold(baseDN, pi.UserDN) && scope > 0 { | ||||
| 			needUsers = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.GroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetGroupOCs()) { | ||||
| 	if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.GroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetGroupOCs()) { | ||||
| 		if baseDN != pi.GroupDN && baseDN != pi.BaseDN || | ||||
| 			baseDN == pi.BaseDN && scope > 1 || | ||||
| 			baseDN == pi.GroupDN && scope > 0 { | ||||
| 			strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 || | ||||
| 			strings.EqualFold(baseDN, pi.GroupDN) && scope > 0 { | ||||
| 			needGroups = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.VirtualGroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetVirtualGroupOCs()) { | ||||
| 	if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.VirtualGroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetVirtualGroupOCs()) { | ||||
| 		if baseDN != pi.VirtualGroupDN && baseDN != pi.BaseDN || | ||||
| 			baseDN == pi.BaseDN && scope > 1 || | ||||
| 			baseDN == pi.VirtualGroupDN && scope > 0 { | ||||
| 			strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 || | ||||
| 			strings.EqualFold(baseDN, pi.VirtualGroupDN) && scope > 0 { | ||||
| 			needUsers = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -36,7 +36,7 @@ func NewDirectSearcher(si server.LDAPServerInstance) *DirectSearcher { | ||||
|  | ||||
| func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) { | ||||
| 	accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access") | ||||
| 	baseDN := strings.ToLower(ds.si.GetBaseDN()) | ||||
| 	baseDN := ds.si.GetBaseDN() | ||||
|  | ||||
| 	filterOC, err := ldap.GetFilterObjectClass(req.Filter) | ||||
| 	if err != nil { | ||||
| @ -59,7 +59,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		}).Inc() | ||||
| 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN) | ||||
| 	} | ||||
| 	if !strings.HasSuffix(req.BindDN, ","+baseDN) { | ||||
| 	if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) { | ||||
| 		metrics.RequestsRejected.With(prometheus.Labels{ | ||||
| 			"outpost_name": ds.si.GetOutpostName(), | ||||
| 			"type":         "search", | ||||
| @ -105,7 +105,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 	scope := req.SearchRequest.Scope | ||||
| 	needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, filterOC) | ||||
|  | ||||
| 	if scope >= 0 && req.BaseDN == baseDN { | ||||
| 	if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) { | ||||
| 		if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) { | ||||
| 			entries = append(entries, ds.si.GetBaseEntry()) | ||||
| 		} | ||||
| @ -209,8 +209,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err | ||||
| 	} | ||||
|  | ||||
| 	if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseUserDN())) { | ||||
| 		singleu := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseUserDN()) | ||||
| 	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)) | ||||
| @ -220,7 +220,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) { | ||||
| 			for _, u := range *users { | ||||
| 				entry := ds.si.UserEntry(u) | ||||
| 				if req.BaseDN == entry.DN || !singleu { | ||||
| 				if strings.EqualFold(req.BaseDN, entry.DN) || !singleu { | ||||
| 					entries = append(entries, entry) | ||||
| 				} | ||||
| 			} | ||||
| @ -229,8 +229,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		scope += 1 // Return the scope to what it was before we descended | ||||
| 	} | ||||
|  | ||||
| 	if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseGroupDN())) { | ||||
| 		singleg := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseGroupDN()) | ||||
| 	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)) | ||||
| @ -240,7 +240,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) { | ||||
| 			for _, g := range *groups { | ||||
| 				entry := group.FromAPIGroup(g, ds.si).Entry() | ||||
| 				if req.BaseDN == entry.DN || !singleg { | ||||
| 				if strings.EqualFold(req.BaseDN, entry.DN) || !singleg { | ||||
| 					entries = append(entries, entry) | ||||
| 				} | ||||
| 			} | ||||
| @ -249,8 +249,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		scope += 1 // Return the scope to what it was before we descended | ||||
| 	} | ||||
|  | ||||
| 	if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseVirtualGroupDN())) { | ||||
| 		singlevg := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseVirtualGroupDN()) | ||||
| 	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)) | ||||
| @ -260,7 +260,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) { | ||||
| 			for _, u := range *users { | ||||
| 				entry := group.FromAPIUser(u, ds.si).Entry() | ||||
| 				if req.BaseDN == entry.DN || !singlevg { | ||||
| 				if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg { | ||||
| 					entries = append(entries, entry) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @ -39,7 +39,7 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher { | ||||
|  | ||||
| func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) { | ||||
| 	accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access") | ||||
| 	baseDN := strings.ToLower(ms.si.GetBaseDN()) | ||||
| 	baseDN := ms.si.GetBaseDN() | ||||
|  | ||||
| 	filterOC, err := ldap.GetFilterObjectClass(req.Filter) | ||||
| 	if err != nil { | ||||
| @ -62,7 +62,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		}).Inc() | ||||
| 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN) | ||||
| 	} | ||||
| 	if !strings.HasSuffix(req.BindDN, ","+baseDN) { | ||||
| 	if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) { | ||||
| 		metrics.RequestsRejected.With(prometheus.Labels{ | ||||
| 			"outpost_name": ms.si.GetOutpostName(), | ||||
| 			"type":         "search", | ||||
| @ -92,7 +92,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 	scope := req.SearchRequest.Scope | ||||
| 	needUsers, needGroups := ms.si.GetNeededObjects(scope, req.BaseDN, filterOC) | ||||
|  | ||||
| 	if scope >= 0 && req.BaseDN == baseDN { | ||||
| 	if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) { | ||||
| 		if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) { | ||||
| 			entries = append(entries, ms.si.GetBaseEntry()) | ||||
| 		} | ||||
| @ -155,8 +155,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err | ||||
| 	} | ||||
|  | ||||
| 	if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseUserDN())) { | ||||
| 		singleu := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseUserDN()) | ||||
| 	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)) | ||||
| @ -166,7 +166,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) { | ||||
| 			for _, u := range *users { | ||||
| 				entry := ms.si.UserEntry(u) | ||||
| 				if req.BaseDN == entry.DN || !singleu { | ||||
| 				if strings.EqualFold(req.BaseDN, entry.DN) || !singleu { | ||||
| 					entries = append(entries, entry) | ||||
| 				} | ||||
| 			} | ||||
| @ -175,8 +175,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		scope += 1 // Return the scope to what it was before we descended | ||||
| 	} | ||||
|  | ||||
| 	if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseGroupDN())) { | ||||
| 		singleg := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseGroupDN()) | ||||
| 	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)) | ||||
| @ -185,7 +185,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
|  | ||||
| 		if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) { | ||||
| 			for _, g := range groups { | ||||
| 				if req.BaseDN == g.DN || !singleg { | ||||
| 				if strings.EqualFold(req.BaseDN, g.DN) || !singleg { | ||||
| 					entries = append(entries, g.Entry()) | ||||
| 				} | ||||
| 			} | ||||
| @ -194,8 +194,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		scope += 1 // Return the scope to what it was before we descended | ||||
| 	} | ||||
|  | ||||
| 	if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseVirtualGroupDN())) { | ||||
| 		singlevg := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseVirtualGroupDN()) | ||||
| 	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)) | ||||
| @ -205,7 +205,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, | ||||
| 		if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) { | ||||
| 			for _, u := range *users { | ||||
| 				entry := group.FromAPIUser(u, ms.si).Entry() | ||||
| 				if req.BaseDN == entry.DN || !singlevg { | ||||
| 				if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg { | ||||
| 					entries = append(entries, entry) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @ -26,7 +26,6 @@ type Request struct { | ||||
| func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Request, *sentry.Span) { | ||||
| 	rid := uuid.New().String() | ||||
| 	bindDN = strings.ToLower(bindDN) | ||||
| 	searchReq.BaseDN = strings.ToLower(searchReq.BaseDN) | ||||
| 	span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search")) | ||||
| 	span.Description = fmt.Sprintf("%s (%s)", searchReq.BaseDN, ldap.ScopeMap[searchReq.Scope]) | ||||
| 	span.SetTag("request_uid", rid) | ||||
|  | ||||
| @ -2,6 +2,7 @@ package utils | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/nmcclain/ldap" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| @ -117,3 +118,7 @@ func GetContainerEntry(filterOC string, dn string, ou string) *ldap.Entry { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func HasSuffixNoCase(s1 string, s2 string) bool { | ||||
| 	return strings.HasSuffix(strings.ToLower(s1), strings.ToLower(s2)) | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| package utils | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	goldap "github.com/go-ldap/ldap/v3" | ||||
| 	ber "github.com/nmcclain/asn1-ber" | ||||
| 	"github.com/nmcclain/ldap" | ||||
| @ -41,7 +43,7 @@ func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet) | ||||
| 	// Switch on type of the value, then check the key | ||||
| 	switch vv := v.(type) { | ||||
| 	case string: | ||||
| 		switch k { | ||||
| 		switch strings.ToLower(k.(string)) { | ||||
| 		case "cn": | ||||
| 			return req.Name(vv), false | ||||
| 		case "member": | ||||
| @ -54,7 +56,7 @@ func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet) | ||||
| 			username := userDN.RDNs[0].Attributes[0].Value | ||||
| 			// If the DN's first ou is virtual-groups, ignore this filter | ||||
| 			if len(userDN.RDNs) > 1 { | ||||
| 				if userDN.RDNs[1].Attributes[0].Value == constants.OUVirtualGroups || userDN.RDNs[1].Attributes[0].Value == constants.OUGroups { | ||||
| 				if strings.EqualFold(userDN.RDNs[1].Attributes[0].Value, constants.OUVirtualGroups) || strings.EqualFold(userDN.RDNs[1].Attributes[0].Value, constants.OUGroups) { | ||||
| 					// Since we know we're not filtering anything, skip this request | ||||
| 					return req, true | ||||
| 				} | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Ilya Kogan
					Ilya Kogan