mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	Map OIDC groups to Orgs/Teams (#21441)
Fixes #19555 Test-Instructions: https://github.com/go-gitea/gitea/pull/21441#issuecomment-1419438000 This PR implements the mapping of user groups provided by OIDC providers to orgs teams in Gitea. The main part is a refactoring of the existing LDAP code to make it usable from different providers. Refactorings: - Moved the router auth code from module to service because of import cycles - Changed some model methods to take a `Context` parameter - Moved the mapping code from LDAP to a common location I've tested it with Keycloak but other providers should work too. The JSON mapping format is the same as for LDAP.  --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		| @@ -10,9 +10,10 @@ import ( | ||||
| 	asymkey_model "code.gitea.io/gitea/models/asymkey" | ||||
| 	"code.gitea.io/gitea/models/auth" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/models/organization" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	auth_module "code.gitea.io/gitea/modules/auth" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	source_service "code.gitea.io/gitea/services/auth/source" | ||||
| 	"code.gitea.io/gitea/services/mailer" | ||||
| 	user_service "code.gitea.io/gitea/services/user" | ||||
| ) | ||||
| @@ -64,61 +65,66 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str | ||||
| 	} | ||||
|  | ||||
| 	if user != nil { | ||||
| 		if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { | ||||
| 			orgCache := make(map[string]*organization.Organization) | ||||
| 			teamCache := make(map[string]*organization.Team) | ||||
| 			source.SyncLdapGroupsToTeams(user, sr.LdapTeamAdd, sr.LdapTeamRemove, orgCache, teamCache) | ||||
| 		} | ||||
| 		if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(user, source.authSource, sr.SSHPublicKey) { | ||||
| 			return user, asymkey_model.RewriteAllPublicKeys() | ||||
| 			if err := asymkey_model.RewriteAllPublicKeys(); err != nil { | ||||
| 				return user, err | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		// Fallback. | ||||
| 		if len(sr.Username) == 0 { | ||||
| 			sr.Username = userName | ||||
| 		} | ||||
|  | ||||
| 		if len(sr.Mail) == 0 { | ||||
| 			sr.Mail = fmt.Sprintf("%s@localhost", sr.Username) | ||||
| 		} | ||||
|  | ||||
| 		user = &user_model.User{ | ||||
| 			LowerName:   strings.ToLower(sr.Username), | ||||
| 			Name:        sr.Username, | ||||
| 			FullName:    composeFullName(sr.Name, sr.Surname, sr.Username), | ||||
| 			Email:       sr.Mail, | ||||
| 			LoginType:   source.authSource.Type, | ||||
| 			LoginSource: source.authSource.ID, | ||||
| 			LoginName:   userName, | ||||
| 			IsAdmin:     sr.IsAdmin, | ||||
| 		} | ||||
| 		overwriteDefault := &user_model.CreateUserOverwriteOptions{ | ||||
| 			IsRestricted: util.OptionalBoolOf(sr.IsRestricted), | ||||
| 			IsActive:     util.OptionalBoolTrue, | ||||
| 		} | ||||
|  | ||||
| 		err := user_model.CreateUser(user, overwriteDefault) | ||||
| 		if err != nil { | ||||
| 			return user, err | ||||
| 		} | ||||
|  | ||||
| 		mailer.SendRegisterNotifyMail(user) | ||||
|  | ||||
| 		if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(user, source.authSource, sr.SSHPublicKey) { | ||||
| 			if err := asymkey_model.RewriteAllPublicKeys(); err != nil { | ||||
| 				return user, err | ||||
| 			} | ||||
| 		} | ||||
| 		if len(source.AttributeAvatar) > 0 { | ||||
| 			if err := user_service.UploadAvatar(user, sr.Avatar); err != nil { | ||||
| 				return user, err | ||||
| 			} | ||||
| 		} | ||||
| 		return user, nil | ||||
| 	} | ||||
|  | ||||
| 	// Fallback. | ||||
| 	if len(sr.Username) == 0 { | ||||
| 		sr.Username = userName | ||||
| 	} | ||||
|  | ||||
| 	if len(sr.Mail) == 0 { | ||||
| 		sr.Mail = fmt.Sprintf("%s@localhost", sr.Username) | ||||
| 	} | ||||
|  | ||||
| 	user = &user_model.User{ | ||||
| 		LowerName:   strings.ToLower(sr.Username), | ||||
| 		Name:        sr.Username, | ||||
| 		FullName:    composeFullName(sr.Name, sr.Surname, sr.Username), | ||||
| 		Email:       sr.Mail, | ||||
| 		LoginType:   source.authSource.Type, | ||||
| 		LoginSource: source.authSource.ID, | ||||
| 		LoginName:   userName, | ||||
| 		IsAdmin:     sr.IsAdmin, | ||||
| 	} | ||||
| 	overwriteDefault := &user_model.CreateUserOverwriteOptions{ | ||||
| 		IsRestricted: util.OptionalBoolOf(sr.IsRestricted), | ||||
| 		IsActive:     util.OptionalBoolTrue, | ||||
| 	} | ||||
|  | ||||
| 	err := user_model.CreateUser(user, overwriteDefault) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	mailer.SendRegisterNotifyMail(user) | ||||
|  | ||||
| 	if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(user, source.authSource, sr.SSHPublicKey) { | ||||
| 		err = asymkey_model.RewriteAllPublicKeys() | ||||
| 	} | ||||
| 	if err == nil && len(source.AttributeAvatar) > 0 { | ||||
| 		_ = user_service.UploadAvatar(user, sr.Avatar) | ||||
| 	} | ||||
| 	if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { | ||||
| 		orgCache := make(map[string]*organization.Organization) | ||||
| 		teamCache := make(map[string]*organization.Team) | ||||
| 		source.SyncLdapGroupsToTeams(user, sr.LdapTeamAdd, sr.LdapTeamRemove, orgCache, teamCache) | ||||
| 		groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap) | ||||
| 		if err != nil { | ||||
| 			return user, err | ||||
| 		} | ||||
| 		if err := source_service.SyncGroupsToTeams(db.DefaultContext, user, sr.Groups, groupTeamMapping, source.GroupTeamMapRemoval); err != nil { | ||||
| 			return user, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return user, err | ||||
| 	return user, nil | ||||
| } | ||||
|  | ||||
| // IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication | ||||
|   | ||||
| @@ -1,94 +0,0 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package ldap | ||||
|  | ||||
| import ( | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/models/organization" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| ) | ||||
|  | ||||
| // SyncLdapGroupsToTeams maps LDAP groups to organization and team memberships | ||||
| func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, ldapTeamRemove map[string][]string, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) { | ||||
| 	var err error | ||||
| 	if source.GroupsEnabled && source.GroupTeamMapRemoval { | ||||
| 		// when the user is not a member of configs LDAP group, remove mapped organizations/teams memberships | ||||
| 		removeMappedMemberships(user, ldapTeamRemove, orgCache, teamCache) | ||||
| 	} | ||||
| 	for orgName, teamNames := range ldapTeamAdd { | ||||
| 		org, ok := orgCache[orgName] | ||||
| 		if !ok { | ||||
| 			org, err = organization.GetOrgByName(orgName) | ||||
| 			if err != nil { | ||||
| 				// organization must be created before LDAP group sync | ||||
| 				log.Warn("LDAP group sync: Could not find organisation %s: %v", orgName, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			orgCache[orgName] = org | ||||
| 		} | ||||
|  | ||||
| 		for _, teamName := range teamNames { | ||||
| 			team, ok := teamCache[orgName+teamName] | ||||
| 			if !ok { | ||||
| 				team, err = org.GetTeam(teamName) | ||||
| 				if err != nil { | ||||
| 					// team must be created before LDAP group sync | ||||
| 					log.Warn("LDAP group sync: Could not find team %s: %v", teamName, err) | ||||
| 					continue | ||||
| 				} | ||||
| 				teamCache[orgName+teamName] = team | ||||
| 			} | ||||
| 			if isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID); !isMember && err == nil { | ||||
| 				log.Trace("LDAP group sync: adding user [%s] to team [%s]", user.Name, org.Name) | ||||
| 			} else { | ||||
| 				continue | ||||
| 			} | ||||
| 			err := models.AddTeamMember(team, user.ID) | ||||
| 			if err != nil { | ||||
| 				log.Error("LDAP group sync: Could not add user to team: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // remove membership to organizations/teams if user is not member of corresponding LDAP group | ||||
| // e.g. lets assume user is member of LDAP group "x", but LDAP group team map contains LDAP groups "x" and "y" | ||||
| // then users membership gets removed for all organizations/teams mapped by LDAP group "y" | ||||
| func removeMappedMemberships(user *user_model.User, ldapTeamRemove map[string][]string, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) { | ||||
| 	var err error | ||||
| 	for orgName, teamNames := range ldapTeamRemove { | ||||
| 		org, ok := orgCache[orgName] | ||||
| 		if !ok { | ||||
| 			org, err = organization.GetOrgByName(orgName) | ||||
| 			if err != nil { | ||||
| 				// organization must be created before LDAP group sync | ||||
| 				log.Warn("LDAP group sync: Could not find organisation %s: %v", orgName, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			orgCache[orgName] = org | ||||
| 		} | ||||
| 		for _, teamName := range teamNames { | ||||
| 			team, ok := teamCache[orgName+teamName] | ||||
| 			if !ok { | ||||
| 				team, err = org.GetTeam(teamName) | ||||
| 				if err != nil { | ||||
| 					// team must must be created before LDAP group sync | ||||
| 					log.Warn("LDAP group sync: Could not find team %s: %v", teamName, err) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			if isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID); isMember && err == nil { | ||||
| 				log.Trace("LDAP group sync: removing user [%s] from team [%s]", user.Name, org.Name) | ||||
| 			} else { | ||||
| 				continue | ||||
| 			} | ||||
| 			err = models.RemoveTeamMember(team, user.ID) | ||||
| 			if err != nil { | ||||
| 				log.Error("LDAP group sync: Could not remove user from team: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -11,26 +11,24 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/json" | ||||
| 	"code.gitea.io/gitea/modules/container" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
|  | ||||
| 	"github.com/go-ldap/ldap/v3" | ||||
| ) | ||||
|  | ||||
| // SearchResult : user data | ||||
| type SearchResult struct { | ||||
| 	Username       string   // Username | ||||
| 	Name           string   // Name | ||||
| 	Surname        string   // Surname | ||||
| 	Mail           string   // E-mail address | ||||
| 	SSHPublicKey   []string // SSH Public Key | ||||
| 	IsAdmin        bool     // if user is administrator | ||||
| 	IsRestricted   bool     // if user is restricted | ||||
| 	LowerName      string   // LowerName | ||||
| 	Avatar         []byte | ||||
| 	LdapTeamAdd    map[string][]string // organizations teams to add | ||||
| 	LdapTeamRemove map[string][]string // organizations teams to remove | ||||
| 	Username     string   // Username | ||||
| 	Name         string   // Name | ||||
| 	Surname      string   // Surname | ||||
| 	Mail         string   // E-mail address | ||||
| 	SSHPublicKey []string // SSH Public Key | ||||
| 	IsAdmin      bool     // if user is administrator | ||||
| 	IsRestricted bool     // if user is restricted | ||||
| 	LowerName    string   // LowerName | ||||
| 	Avatar       []byte | ||||
| 	Groups       container.Set[string] | ||||
| } | ||||
|  | ||||
| func (source *Source) sanitizedUserQuery(username string) (string, bool) { | ||||
| @@ -196,9 +194,8 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { | ||||
| } | ||||
|  | ||||
| // List all group memberships of a user | ||||
| func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGroupFilter bool) []string { | ||||
| 	var ldapGroups []string | ||||
| 	var searchFilter string | ||||
| func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGroupFilter bool) container.Set[string] { | ||||
| 	ldapGroups := make(container.Set[string]) | ||||
|  | ||||
| 	groupFilter, ok := source.sanitizedGroupFilter(source.GroupFilter) | ||||
| 	if !ok { | ||||
| @@ -210,12 +207,12 @@ func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGr | ||||
| 		return ldapGroups | ||||
| 	} | ||||
|  | ||||
| 	var searchFilter string | ||||
| 	if applyGroupFilter { | ||||
| 		searchFilter = fmt.Sprintf("(&(%s)(%s=%s))", groupFilter, source.GroupMemberUID, ldap.EscapeFilter(uid)) | ||||
| 	} else { | ||||
| 		searchFilter = fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid)) | ||||
| 	} | ||||
|  | ||||
| 	result, err := l.Search(ldap.NewSearchRequest( | ||||
| 		groupDN, | ||||
| 		ldap.ScopeWholeSubtree, | ||||
| @@ -237,44 +234,12 @@ func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGr | ||||
| 			log.Error("LDAP search was successful, but found no DN!") | ||||
| 			continue | ||||
| 		} | ||||
| 		ldapGroups = append(ldapGroups, entry.DN) | ||||
| 		ldapGroups.Add(entry.DN) | ||||
| 	} | ||||
|  | ||||
| 	return ldapGroups | ||||
| } | ||||
|  | ||||
| // parse LDAP groups and return map of ldap groups to organizations teams | ||||
| func (source *Source) mapLdapGroupsToTeams() map[string]map[string][]string { | ||||
| 	ldapGroupsToTeams := make(map[string]map[string][]string) | ||||
| 	err := json.Unmarshal([]byte(source.GroupTeamMap), &ldapGroupsToTeams) | ||||
| 	if err != nil { | ||||
| 		log.Error("Failed to unmarshall LDAP teams map: %v", err) | ||||
| 		return ldapGroupsToTeams | ||||
| 	} | ||||
| 	return ldapGroupsToTeams | ||||
| } | ||||
|  | ||||
| // getMappedMemberships : returns the organizations and teams to modify the users membership | ||||
| func (source *Source) getMappedMemberships(usersLdapGroups []string, uid string) (map[string][]string, map[string][]string) { | ||||
| 	// unmarshall LDAP group team map from configs | ||||
| 	ldapGroupsToTeams := source.mapLdapGroupsToTeams() | ||||
| 	membershipsToAdd := map[string][]string{} | ||||
| 	membershipsToRemove := map[string][]string{} | ||||
| 	for group, memberships := range ldapGroupsToTeams { | ||||
| 		isUserInGroup := util.SliceContainsString(usersLdapGroups, group) | ||||
| 		if isUserInGroup { | ||||
| 			for org, teams := range memberships { | ||||
| 				membershipsToAdd[org] = teams | ||||
| 			} | ||||
| 		} else if !isUserInGroup { | ||||
| 			for org, teams := range memberships { | ||||
| 				membershipsToRemove[org] = teams | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return membershipsToAdd, membershipsToRemove | ||||
| } | ||||
|  | ||||
| func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string { | ||||
| 	if strings.ToLower(source.UserUID) == "dn" { | ||||
| 		return entry.DN | ||||
| @@ -399,23 +364,6 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR | ||||
| 	surname := sr.Entries[0].GetAttributeValue(source.AttributeSurname) | ||||
| 	mail := sr.Entries[0].GetAttributeValue(source.AttributeMail) | ||||
|  | ||||
| 	teamsToAdd := make(map[string][]string) | ||||
| 	teamsToRemove := make(map[string][]string) | ||||
|  | ||||
| 	// Check group membership | ||||
| 	if source.GroupsEnabled { | ||||
| 		userAttributeListedInGroup := source.getUserAttributeListedInGroup(sr.Entries[0]) | ||||
| 		usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, true) | ||||
|  | ||||
| 		if source.GroupFilter != "" && len(usersLdapGroups) == 0 { | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		if source.GroupTeamMap != "" || source.GroupTeamMapRemoval { | ||||
| 			teamsToAdd, teamsToRemove = source.getMappedMemberships(usersLdapGroups, userAttributeListedInGroup) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if isAttributeSSHPublicKeySet { | ||||
| 		sshPublicKey = sr.Entries[0].GetAttributeValues(source.AttributeSSHPublicKey) | ||||
| 	} | ||||
| @@ -431,6 +379,17 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR | ||||
| 		Avatar = sr.Entries[0].GetRawAttributeValue(source.AttributeAvatar) | ||||
| 	} | ||||
|  | ||||
| 	// Check group membership | ||||
| 	var usersLdapGroups container.Set[string] | ||||
| 	if source.GroupsEnabled { | ||||
| 		userAttributeListedInGroup := source.getUserAttributeListedInGroup(sr.Entries[0]) | ||||
| 		usersLdapGroups = source.listLdapGroupMemberships(l, userAttributeListedInGroup, true) | ||||
|  | ||||
| 		if source.GroupFilter != "" && len(usersLdapGroups) == 0 { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !directBind && source.AttributesInBind { | ||||
| 		// binds user (checking password) after looking-up attributes in BindDN context | ||||
| 		err = bindUser(l, userDN, passwd) | ||||
| @@ -440,17 +399,16 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR | ||||
| 	} | ||||
|  | ||||
| 	return &SearchResult{ | ||||
| 		LowerName:      strings.ToLower(username), | ||||
| 		Username:       username, | ||||
| 		Name:           firstname, | ||||
| 		Surname:        surname, | ||||
| 		Mail:           mail, | ||||
| 		SSHPublicKey:   sshPublicKey, | ||||
| 		IsAdmin:        isAdmin, | ||||
| 		IsRestricted:   isRestricted, | ||||
| 		Avatar:         Avatar, | ||||
| 		LdapTeamAdd:    teamsToAdd, | ||||
| 		LdapTeamRemove: teamsToRemove, | ||||
| 		LowerName:    strings.ToLower(username), | ||||
| 		Username:     username, | ||||
| 		Name:         firstname, | ||||
| 		Surname:      surname, | ||||
| 		Mail:         mail, | ||||
| 		SSHPublicKey: sshPublicKey, | ||||
| 		IsAdmin:      isAdmin, | ||||
| 		IsRestricted: isRestricted, | ||||
| 		Avatar:       Avatar, | ||||
| 		Groups:       usersLdapGroups, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -512,33 +470,29 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) { | ||||
| 	result := make([]*SearchResult, 0, len(sr.Entries)) | ||||
|  | ||||
| 	for _, v := range sr.Entries { | ||||
| 		teamsToAdd := make(map[string][]string) | ||||
| 		teamsToRemove := make(map[string][]string) | ||||
|  | ||||
| 		var usersLdapGroups container.Set[string] | ||||
| 		if source.GroupsEnabled { | ||||
| 			userAttributeListedInGroup := source.getUserAttributeListedInGroup(v) | ||||
|  | ||||
| 			if source.GroupFilter != "" { | ||||
| 				usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, true) | ||||
| 				usersLdapGroups = source.listLdapGroupMemberships(l, userAttributeListedInGroup, true) | ||||
| 				if len(usersLdapGroups) == 0 { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if source.GroupTeamMap != "" || source.GroupTeamMapRemoval { | ||||
| 				usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, false) | ||||
| 				teamsToAdd, teamsToRemove = source.getMappedMemberships(usersLdapGroups, userAttributeListedInGroup) | ||||
| 				usersLdapGroups = source.listLdapGroupMemberships(l, userAttributeListedInGroup, false) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		user := &SearchResult{ | ||||
| 			Username:       v.GetAttributeValue(source.AttributeUsername), | ||||
| 			Name:           v.GetAttributeValue(source.AttributeName), | ||||
| 			Surname:        v.GetAttributeValue(source.AttributeSurname), | ||||
| 			Mail:           v.GetAttributeValue(source.AttributeMail), | ||||
| 			IsAdmin:        checkAdmin(l, source, v.DN), | ||||
| 			LdapTeamAdd:    teamsToAdd, | ||||
| 			LdapTeamRemove: teamsToRemove, | ||||
| 			Username: v.GetAttributeValue(source.AttributeUsername), | ||||
| 			Name:     v.GetAttributeValue(source.AttributeName), | ||||
| 			Surname:  v.GetAttributeValue(source.AttributeSurname), | ||||
| 			Mail:     v.GetAttributeValue(source.AttributeMail), | ||||
| 			IsAdmin:  checkAdmin(l, source, v.DN), | ||||
| 			Groups:   usersLdapGroups, | ||||
| 		} | ||||
|  | ||||
| 		if !user.IsAdmin { | ||||
|   | ||||
| @@ -13,8 +13,10 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/models/organization" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	auth_module "code.gitea.io/gitea/modules/auth" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	source_service "code.gitea.io/gitea/services/auth/source" | ||||
| 	user_service "code.gitea.io/gitea/services/user" | ||||
| ) | ||||
|  | ||||
| @@ -65,6 +67,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { | ||||
| 	orgCache := make(map[string]*organization.Organization) | ||||
| 	teamCache := make(map[string]*organization.Team) | ||||
|  | ||||
| 	groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, su := range sr { | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| @@ -173,7 +180,9 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { | ||||
| 		} | ||||
| 		// Synchronize LDAP groups with organization and team memberships | ||||
| 		if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { | ||||
| 			source.SyncLdapGroupsToTeams(usr, su.LdapTeamAdd, su.LdapTeamRemove, orgCache, teamCache) | ||||
| 			if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.Groups, groupTeamMapping, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil { | ||||
| 				log.Error("SyncGroupsToTeamsCached: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user