code.gitea.io/gitea@v1.22.3/services/auth/source/ldap/source_sync.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package ldap 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 11 asymkey_model "code.gitea.io/gitea/models/asymkey" 12 "code.gitea.io/gitea/models/db" 13 "code.gitea.io/gitea/models/organization" 14 user_model "code.gitea.io/gitea/models/user" 15 auth_module "code.gitea.io/gitea/modules/auth" 16 "code.gitea.io/gitea/modules/container" 17 "code.gitea.io/gitea/modules/log" 18 "code.gitea.io/gitea/modules/optional" 19 asymkey_service "code.gitea.io/gitea/services/asymkey" 20 source_service "code.gitea.io/gitea/services/auth/source" 21 user_service "code.gitea.io/gitea/services/user" 22 ) 23 24 // Sync causes this ldap source to synchronize its users with the db 25 func (source *Source) Sync(ctx context.Context, updateExisting bool) error { 26 log.Trace("Doing: SyncExternalUsers[%s]", source.authSource.Name) 27 28 isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 29 var sshKeysNeedUpdate bool 30 31 // Find all users with this login type - FIXME: Should this be an iterator? 32 users, err := user_model.GetUsersBySource(ctx, source.authSource) 33 if err != nil { 34 log.Error("SyncExternalUsers: %v", err) 35 return err 36 } 37 select { 38 case <-ctx.Done(): 39 log.Warn("SyncExternalUsers: Cancelled before update of %s", source.authSource.Name) 40 return db.ErrCancelledf("Before update of %s", source.authSource.Name) 41 default: 42 } 43 44 usernameUsers := make(map[string]*user_model.User, len(users)) 45 mailUsers := make(map[string]*user_model.User, len(users)) 46 keepActiveUsers := make(container.Set[int64]) 47 48 for _, u := range users { 49 usernameUsers[u.LowerName] = u 50 mailUsers[strings.ToLower(u.Email)] = u 51 } 52 53 sr, err := source.SearchEntries() 54 if err != nil { 55 log.Error("SyncExternalUsers LDAP source failure [%s], skipped", source.authSource.Name) 56 return nil 57 } 58 59 if len(sr) == 0 { 60 if !source.AllowDeactivateAll { 61 log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users") 62 return nil 63 } 64 log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings") 65 } 66 67 orgCache := make(map[string]*organization.Organization) 68 teamCache := make(map[string]*organization.Team) 69 70 groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap) 71 if err != nil { 72 return err 73 } 74 75 for _, su := range sr { 76 select { 77 case <-ctx.Done(): 78 log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", source.authSource.Name) 79 // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed 80 if sshKeysNeedUpdate { 81 err = asymkey_service.RewriteAllPublicKeys(ctx) 82 if err != nil { 83 log.Error("RewriteAllPublicKeys: %v", err) 84 } 85 } 86 return db.ErrCancelledf("During update of %s before completed update of users", source.authSource.Name) 87 default: 88 } 89 if len(su.Username) == 0 && len(su.Mail) == 0 { 90 continue 91 } 92 93 var usr *user_model.User 94 if len(su.Username) > 0 { 95 usr = usernameUsers[su.LowerName] 96 } 97 if usr == nil && len(su.Mail) > 0 { 98 usr = mailUsers[strings.ToLower(su.Mail)] 99 } 100 101 if usr != nil { 102 keepActiveUsers.Add(usr.ID) 103 } else if len(su.Username) == 0 { 104 // we cannot create the user if su.Username is empty 105 continue 106 } 107 108 if len(su.Mail) == 0 { 109 su.Mail = fmt.Sprintf("%s@localhost.local", su.Username) 110 } 111 112 fullName := composeFullName(su.Name, su.Surname, su.Username) 113 // If no existing user found, create one 114 if usr == nil { 115 log.Trace("SyncExternalUsers[%s]: Creating user %s", source.authSource.Name, su.Username) 116 117 usr = &user_model.User{ 118 LowerName: su.LowerName, 119 Name: su.Username, 120 FullName: fullName, 121 LoginType: source.authSource.Type, 122 LoginSource: source.authSource.ID, 123 LoginName: su.Username, 124 Email: su.Mail, 125 IsAdmin: su.IsAdmin, 126 } 127 overwriteDefault := &user_model.CreateUserOverwriteOptions{ 128 IsRestricted: optional.Some(su.IsRestricted), 129 IsActive: optional.Some(true), 130 } 131 132 err = user_model.CreateUser(ctx, usr, overwriteDefault) 133 if err != nil { 134 log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) 135 } 136 137 if err == nil && isAttributeSSHPublicKeySet { 138 log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) 139 if asymkey_model.AddPublicKeysBySource(ctx, usr, source.authSource, su.SSHPublicKey) { 140 sshKeysNeedUpdate = true 141 } 142 } 143 144 if err == nil && len(source.AttributeAvatar) > 0 { 145 _ = user_service.UploadAvatar(ctx, usr, su.Avatar) 146 } 147 } else if updateExisting { 148 // Synchronize SSH Public Key if that attribute is set 149 if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, usr, source.authSource, su.SSHPublicKey) { 150 sshKeysNeedUpdate = true 151 } 152 153 // Check if user data has changed 154 if (len(source.AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || 155 (len(source.RestrictedFilter) > 0 && usr.IsRestricted != su.IsRestricted) || 156 !strings.EqualFold(usr.Email, su.Mail) || 157 usr.FullName != fullName || 158 !usr.IsActive { 159 log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name) 160 161 opts := &user_service.UpdateOptions{ 162 FullName: optional.Some(fullName), 163 IsActive: optional.Some(true), 164 } 165 if source.AdminFilter != "" { 166 opts.IsAdmin = optional.Some(su.IsAdmin) 167 } 168 // Change existing restricted flag only if RestrictedFilter option is set 169 if !su.IsAdmin && source.RestrictedFilter != "" { 170 opts.IsRestricted = optional.Some(su.IsRestricted) 171 } 172 173 if err := user_service.UpdateUser(ctx, usr, opts); err != nil { 174 log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err) 175 } 176 177 if err := user_service.ReplacePrimaryEmailAddress(ctx, usr, su.Mail); err != nil { 178 log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.authSource.Name, usr.Name, su.Mail, err) 179 } 180 } 181 182 if usr.IsUploadAvatarChanged(su.Avatar) { 183 if err == nil && len(source.AttributeAvatar) > 0 { 184 _ = user_service.UploadAvatar(ctx, usr, su.Avatar) 185 } 186 } 187 } 188 // Synchronize LDAP groups with organization and team memberships 189 if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { 190 if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.Groups, groupTeamMapping, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil { 191 log.Error("SyncGroupsToTeamsCached: %v", err) 192 } 193 } 194 } 195 196 // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed 197 if sshKeysNeedUpdate { 198 err = asymkey_service.RewriteAllPublicKeys(ctx) 199 if err != nil { 200 log.Error("RewriteAllPublicKeys: %v", err) 201 } 202 } 203 204 select { 205 case <-ctx.Done(): 206 log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", source.authSource.Name) 207 return db.ErrCancelledf("During update of %s before delete users", source.authSource.Name) 208 default: 209 } 210 211 // Deactivate users not present in LDAP 212 if updateExisting { 213 for _, usr := range users { 214 if keepActiveUsers.Contains(usr.ID) { 215 continue 216 } 217 218 log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name) 219 220 opts := &user_service.UpdateOptions{ 221 IsActive: optional.Some(false), 222 } 223 if err := user_service.UpdateUser(ctx, usr, opts); err != nil { 224 log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) 225 } 226 } 227 } 228 return nil 229 }