mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-05 15:43:12 +01:00
08f5a25d3b
When the ldap synchronizer is look for an email address and fails at finding one, it falls back at creating one using "localhost.local" domain. This new field makes this domain name configurable. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3414 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Baptiste Daroussin <bapt@FreeBSD.org> Co-committed-by: Baptiste Daroussin <bapt@FreeBSD.org>
233 lines
7.6 KiB
Go
233 lines
7.6 KiB
Go
// 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
|
|
}
|