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  }