232 lines · 7.5 KB · Go
RawDownload
// Copyright 2021 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package ldap import (	"context"	"fmt"	"strings" 	asymkey_model "forgejo.org/models/asymkey"	"forgejo.org/models/db"	"forgejo.org/models/organization"	user_model "forgejo.org/models/user"	auth_module "forgejo.org/modules/auth"	"forgejo.org/modules/container"	"forgejo.org/modules/log"	"forgejo.org/modules/optional"	source_service "forgejo.org/services/auth/source"	user_service "forgejo.org/services/user") // Sync causes this ldap source to synchronize its users with the dbfunc (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}