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