summaryrefslogtreecommitdiffstats
path: root/services/auth/source/ldap/source_sync.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /services/auth/source/ldap/source_sync.go
parentInitial commit. (diff)
downloadforgejo-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.go232
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
+}