loading up the forgejo repo on tangled to test page performance
1// Copyright 2021 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package ldap
5
6import (
7 "context"
8 "fmt"
9 "strings"
10
11 asymkey_model "forgejo.org/models/asymkey"
12 "forgejo.org/models/db"
13 "forgejo.org/models/organization"
14 user_model "forgejo.org/models/user"
15 auth_module "forgejo.org/modules/auth"
16 "forgejo.org/modules/container"
17 "forgejo.org/modules/log"
18 "forgejo.org/modules/optional"
19 source_service "forgejo.org/services/auth/source"
20 user_service "forgejo.org/services/user"
21)
22
23// Sync causes this ldap source to synchronize its users with the db
24func (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 domainName := source.DefaultDomainName
109 if len(domainName) == 0 {
110 domainName = "localhost.local"
111 }
112 su.Mail = fmt.Sprintf("%s@%s", su.Username, domainName)
113 }
114
115 fullName := composeFullName(su.Name, su.Surname, su.Username)
116 // If no existing user found, create one
117 if usr == nil {
118 log.Trace("SyncExternalUsers[%s]: Creating user %s", source.authSource.Name, su.Username)
119
120 usr = &user_model.User{
121 LowerName: su.LowerName,
122 Name: su.Username,
123 FullName: fullName,
124 LoginType: source.authSource.Type,
125 LoginSource: source.authSource.ID,
126 LoginName: su.Username,
127 Email: su.Mail,
128 IsAdmin: su.IsAdmin,
129 }
130 overwriteDefault := &user_model.CreateUserOverwriteOptions{
131 IsRestricted: optional.Some(su.IsRestricted),
132 IsActive: optional.Some(true),
133 }
134
135 err = user_model.CreateUser(ctx, usr, overwriteDefault)
136 if err != nil {
137 log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err)
138 }
139
140 if err == nil && isAttributeSSHPublicKeySet {
141 log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name)
142 if asymkey_model.AddPublicKeysBySource(ctx, usr, source.authSource, su.SSHPublicKey) {
143 sshKeysNeedUpdate = true
144 }
145 }
146
147 if err == nil && len(source.AttributeAvatar) > 0 {
148 _ = user_service.UploadAvatar(ctx, usr, su.Avatar)
149 }
150 } else if updateExisting {
151 // Synchronize SSH Public Key if that attribute is set
152 if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, usr, source.authSource, su.SSHPublicKey) {
153 sshKeysNeedUpdate = true
154 }
155
156 // Check if user data has changed
157 if (len(source.AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) ||
158 (len(source.RestrictedFilter) > 0 && usr.IsRestricted != su.IsRestricted) ||
159 !strings.EqualFold(usr.Email, su.Mail) ||
160 usr.FullName != fullName ||
161 !usr.IsActive {
162 log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name)
163
164 opts := &user_service.UpdateOptions{
165 FullName: optional.Some(fullName),
166 IsActive: optional.Some(true),
167 }
168 if source.AdminFilter != "" {
169 opts.IsAdmin = optional.Some(su.IsAdmin)
170 }
171 // Change existing restricted flag only if RestrictedFilter option is set
172 if !su.IsAdmin && source.RestrictedFilter != "" {
173 opts.IsRestricted = optional.Some(su.IsRestricted)
174 }
175
176 if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
177 log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err)
178 }
179
180 if err := user_service.ReplacePrimaryEmailAddress(ctx, usr, su.Mail); err != nil {
181 log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.authSource.Name, usr.Name, su.Mail, err)
182 }
183 }
184
185 if usr.IsUploadAvatarChanged(su.Avatar) {
186 if err == nil && len(source.AttributeAvatar) > 0 {
187 _ = user_service.UploadAvatar(ctx, usr, su.Avatar)
188 }
189 }
190 }
191 // Synchronize LDAP groups with organization and team memberships
192 if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) {
193 if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.Groups, groupTeamMapping, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil {
194 log.Error("SyncGroupsToTeamsCached: %v", err)
195 }
196 }
197 }
198
199 // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
200 if sshKeysNeedUpdate {
201 err = asymkey_model.RewriteAllPublicKeys(ctx)
202 if err != nil {
203 log.Error("RewriteAllPublicKeys: %v", err)
204 }
205 }
206
207 select {
208 case <-ctx.Done():
209 log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", source.authSource.Name)
210 return db.ErrCancelledf("During update of %s before delete users", source.authSource.Name)
211 default:
212 }
213
214 // Deactivate users not present in LDAP
215 if updateExisting {
216 for _, usr := range users {
217 if keepActiveUsers.Contains(usr.ID) {
218 continue
219 }
220
221 log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name)
222
223 opts := &user_service.UpdateOptions{
224 IsActive: optional.Some(false),
225 }
226 if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
227 log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err)
228 }
229 }
230 }
231 return nil
232}