mirror of
https://github.com/go-gitea/gitea.git
synced 2025-06-20 13:40:29 +02:00
This fixes 3 issues I encountered when debugging problems with our LDAP sync: 1. The comparison of the hashed image data in `IsUploadAvatarChanged` is wrong. It seems to be from before avatar hashing was changed and unified in #22289. This results in the function always returning `true` for any avatars, even if they weren't changed. 2. Even if there's no avatar to upload (i.e. no avatar available for the LDAP entry), the upload function would still be called for every single user, only to then fail, because the data isn't valid. This is unnecessary. 3. Another small issue is that the comparison function (and thus hashing of data) is called for every user, even if there is no avatar attribute configured at all for the LDAP source. Thus, I switched the condition nesting, so that no cycles are wasted when avatar sync isn't configured in the first place. I also added a trace log for when there is actually a new avatar being uploaded for an existing user, which is now only shown when that is actually the case. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
230 lines
7.6 KiB
Go
230 lines
7.6 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package ldap
|
|
|
|
import (
|
|
"context"
|
|
"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"
|
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
|
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 := strings.TrimSpace(source.AttributeSSHPublicKey) != ""
|
|
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_service.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 su.Username == "" && su.Mail == "" {
|
|
continue
|
|
}
|
|
|
|
var usr *user_model.User
|
|
if su.Username != "" {
|
|
usr = usernameUsers[su.LowerName]
|
|
}
|
|
if usr == nil && su.Mail != "" {
|
|
usr = mailUsers[strings.ToLower(su.Mail)]
|
|
}
|
|
|
|
if usr != nil {
|
|
keepActiveUsers.Add(usr.ID)
|
|
} else if su.Username == "" {
|
|
// we cannot create the user if su.Username is empty
|
|
continue
|
|
}
|
|
|
|
if su.Mail == "" {
|
|
su.Mail = su.Username + "@localhost.local"
|
|
}
|
|
|
|
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, &user_model.Meta{}, 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 && source.AttributeAvatar != "" {
|
|
_ = 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 (source.AdminFilter != "" && usr.IsAdmin != su.IsAdmin) ||
|
|
(source.RestrictedFilter != "" && 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 source.AttributeAvatar != "" {
|
|
if len(su.Avatar) > 0 && usr.IsUploadAvatarChanged(su.Avatar) {
|
|
log.Trace("SyncExternalUsers[%s]: Uploading new avatar for %s", source.AuthSource.Name, usr.Name)
|
|
_ = 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_service.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
|
|
}
|