diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-12-12 23:57:56 +0100 |
commit | e68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch) | |
tree | 97775d6c13b0f416af55314eb6a89ef792474615 /services/auth/source/ldap/source_sync.go | |
parent | Initial commit. (diff) | |
download | forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip |
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'services/auth/source/ldap/source_sync.go')
-rw-r--r-- | services/auth/source/ldap/source_sync.go | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go new file mode 100644 index 0000000..1f70eda --- /dev/null +++ b/services/auth/source/ldap/source_sync.go @@ -0,0 +1,232 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package ldap + +import ( + "context" + "fmt" + "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" + 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 := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 + 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_model.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 len(su.Username) == 0 && len(su.Mail) == 0 { + continue + } + + var usr *user_model.User + if len(su.Username) > 0 { + usr = usernameUsers[su.LowerName] + } + if usr == nil && len(su.Mail) > 0 { + usr = mailUsers[strings.ToLower(su.Mail)] + } + + if usr != nil { + keepActiveUsers.Add(usr.ID) + } else if len(su.Username) == 0 { + // we cannot create the user if su.Username is empty + continue + } + + if len(su.Mail) == 0 { + domainName := source.DefaultDomainName + if len(domainName) == 0 { + domainName = "localhost.local" + } + su.Mail = fmt.Sprintf("%s@%s", su.Username, domainName) + } + + 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, 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 && len(source.AttributeAvatar) > 0 { + _ = 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 (len(source.AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || + (len(source.RestrictedFilter) > 0 && 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 = optional.Some(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 usr.IsUploadAvatarChanged(su.Avatar) { + if err == nil && len(source.AttributeAvatar) > 0 { + _ = 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_model.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 +} |