mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	
		
			
				
	
	
		
			230 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2021 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package ldap
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"strings"
 | |
| 
 | |
| 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 | |
| 	"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/container"
 | |
| 	"code.gitea.io/gitea/modules/log"
 | |
| 	"code.gitea.io/gitea/modules/optional"
 | |
| 	asymkey_service "code.gitea.io/gitea/services/asymkey"
 | |
| 	source_service "code.gitea.io/gitea/services/auth/source"
 | |
| 	user_service "code.gitea.io/gitea/services/user"
 | |
| )
 | |
| 
 | |
| // Sync causes this ldap source to synchronize its users with the db
 | |
| func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
 | |
| 	log.Trace("Doing: SyncExternalUsers[%s]", source.AuthSource.Name)
 | |
| 
 | |
| 	isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != ""
 | |
| 	var sshKeysNeedUpdate bool
 | |
| 
 | |
| 	// Find all users with this login type - FIXME: Should this be an iterator?
 | |
| 	users, err := user_model.GetUsersBySource(ctx, source.AuthSource)
 | |
| 	if err != nil {
 | |
| 		log.Error("SyncExternalUsers: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	select {
 | |
| 	case <-ctx.Done():
 | |
| 		log.Warn("SyncExternalUsers: Cancelled before update of %s", source.AuthSource.Name)
 | |
| 		return db.ErrCancelledf("Before update of %s", source.AuthSource.Name)
 | |
| 	default:
 | |
| 	}
 | |
| 
 | |
| 	usernameUsers := make(map[string]*user_model.User, len(users))
 | |
| 	mailUsers := make(map[string]*user_model.User, len(users))
 | |
| 	keepActiveUsers := make(container.Set[int64])
 | |
| 
 | |
| 	for _, u := range users {
 | |
| 		usernameUsers[u.LowerName] = u
 | |
| 		mailUsers[strings.ToLower(u.Email)] = u
 | |
| 	}
 | |
| 
 | |
| 	sr, err := source.SearchEntries()
 | |
| 	if err != nil {
 | |
| 		log.Error("SyncExternalUsers LDAP source failure [%s], skipped", source.AuthSource.Name)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if len(sr) == 0 {
 | |
| 		if !source.AllowDeactivateAll {
 | |
| 			log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
 | |
| 			return nil
 | |
| 		}
 | |
| 		log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
 | |
| 	}
 | |
| 
 | |
| 	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():
 | |
| 			log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", source.AuthSource.Name)
 | |
| 			// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
 | |
| 			if sshKeysNeedUpdate {
 | |
| 				err = asymkey_service.RewriteAllPublicKeys(ctx)
 | |
| 				if err != nil {
 | |
| 					log.Error("RewriteAllPublicKeys: %v", err)
 | |
| 				}
 | |
| 			}
 | |
| 			return db.ErrCancelledf("During update of %s before completed update of users", source.AuthSource.Name)
 | |
| 		default:
 | |
| 		}
 | |
| 		if su.Username == "" && su.Mail == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		var usr *user_model.User
 | |
| 		if su.Username != "" {
 | |
| 			usr = usernameUsers[su.LowerName]
 | |
| 		}
 | |
| 		if usr == nil && su.Mail != "" {
 | |
| 			usr = mailUsers[strings.ToLower(su.Mail)]
 | |
| 		}
 | |
| 
 | |
| 		if usr != nil {
 | |
| 			keepActiveUsers.Add(usr.ID)
 | |
| 		} else if su.Username == "" {
 | |
| 			// we cannot create the user if su.Username is empty
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if su.Mail == "" {
 | |
| 			su.Mail = su.Username + "@localhost.local"
 | |
| 		}
 | |
| 
 | |
| 		fullName := composeFullName(su.Name, su.Surname, su.Username)
 | |
| 		// If no existing user found, create one
 | |
| 		if usr == nil {
 | |
| 			log.Trace("SyncExternalUsers[%s]: Creating user %s", source.AuthSource.Name, su.Username)
 | |
| 
 | |
| 			usr = &user_model.User{
 | |
| 				LowerName:   su.LowerName,
 | |
| 				Name:        su.Username,
 | |
| 				FullName:    fullName,
 | |
| 				LoginType:   source.AuthSource.Type,
 | |
| 				LoginSource: source.AuthSource.ID,
 | |
| 				LoginName:   su.Username,
 | |
| 				Email:       su.Mail,
 | |
| 				IsAdmin:     su.IsAdmin,
 | |
| 			}
 | |
| 			overwriteDefault := &user_model.CreateUserOverwriteOptions{
 | |
| 				IsRestricted: optional.Some(su.IsRestricted),
 | |
| 				IsActive:     optional.Some(true),
 | |
| 			}
 | |
| 
 | |
| 			err = user_model.CreateUser(ctx, usr, &user_model.Meta{}, overwriteDefault)
 | |
| 			if err != nil {
 | |
| 				log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.AuthSource.Name, su.Username, err)
 | |
| 			}
 | |
| 
 | |
| 			if err == nil && isAttributeSSHPublicKeySet {
 | |
| 				log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.AuthSource.Name, usr.Name)
 | |
| 				if asymkey_model.AddPublicKeysBySource(ctx, usr, source.AuthSource, su.SSHPublicKey) {
 | |
| 					sshKeysNeedUpdate = true
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if err == nil && source.AttributeAvatar != "" {
 | |
| 				_ = user_service.UploadAvatar(ctx, usr, su.Avatar)
 | |
| 			}
 | |
| 		} else if updateExisting {
 | |
| 			// Synchronize SSH Public Key if that attribute is set
 | |
| 			if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, usr, source.AuthSource, su.SSHPublicKey) {
 | |
| 				sshKeysNeedUpdate = true
 | |
| 			}
 | |
| 
 | |
| 			// Check if user data has changed
 | |
| 			if (source.AdminFilter != "" && usr.IsAdmin != su.IsAdmin) ||
 | |
| 				(source.RestrictedFilter != "" && usr.IsRestricted != su.IsRestricted) ||
 | |
| 				!strings.EqualFold(usr.Email, su.Mail) ||
 | |
| 				usr.FullName != fullName ||
 | |
| 				!usr.IsActive {
 | |
| 				log.Trace("SyncExternalUsers[%s]: Updating user %s", source.AuthSource.Name, usr.Name)
 | |
| 
 | |
| 				opts := &user_service.UpdateOptions{
 | |
| 					FullName: optional.Some(fullName),
 | |
| 					IsActive: optional.Some(true),
 | |
| 				}
 | |
| 				if source.AdminFilter != "" {
 | |
| 					opts.IsAdmin = user_service.UpdateOptionFieldFromSync(su.IsAdmin)
 | |
| 				}
 | |
| 				// Change existing restricted flag only if RestrictedFilter option is set
 | |
| 				if !su.IsAdmin && source.RestrictedFilter != "" {
 | |
| 					opts.IsRestricted = optional.Some(su.IsRestricted)
 | |
| 				}
 | |
| 
 | |
| 				if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
 | |
| 					log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.AuthSource.Name, usr.Name, err)
 | |
| 				}
 | |
| 
 | |
| 				if err := user_service.ReplacePrimaryEmailAddress(ctx, usr, su.Mail); err != nil {
 | |
| 					log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.AuthSource.Name, usr.Name, su.Mail, err)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if source.AttributeAvatar != "" {
 | |
| 				if len(su.Avatar) > 0 && usr.IsUploadAvatarChanged(su.Avatar) {
 | |
| 					log.Trace("SyncExternalUsers[%s]: Uploading new avatar for %s", source.AuthSource.Name, usr.Name)
 | |
| 					_ = user_service.UploadAvatar(ctx, usr, su.Avatar)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		// Synchronize LDAP groups with organization and team memberships
 | |
| 		if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) {
 | |
| 			if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.Groups, groupTeamMapping, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil {
 | |
| 				log.Error("SyncGroupsToTeamsCached: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
 | |
| 	if sshKeysNeedUpdate {
 | |
| 		err = asymkey_service.RewriteAllPublicKeys(ctx)
 | |
| 		if err != nil {
 | |
| 			log.Error("RewriteAllPublicKeys: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	select {
 | |
| 	case <-ctx.Done():
 | |
| 		log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", source.AuthSource.Name)
 | |
| 		return db.ErrCancelledf("During update of %s before delete users", source.AuthSource.Name)
 | |
| 	default:
 | |
| 	}
 | |
| 
 | |
| 	// Deactivate users not present in LDAP
 | |
| 	if updateExisting {
 | |
| 		for _, usr := range users {
 | |
| 			if keepActiveUsers.Contains(usr.ID) {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.AuthSource.Name, usr.Name)
 | |
| 
 | |
| 			opts := &user_service.UpdateOptions{
 | |
| 				IsActive: optional.Some(false),
 | |
| 			}
 | |
| 			if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
 | |
| 				log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.AuthSource.Name, usr.Name, err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |