code.gitea.io/gitea@v1.22.3/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/optional"
    19  	asymkey_service "code.gitea.io/gitea/services/asymkey"
    20  	source_service "code.gitea.io/gitea/services/auth/source"
    21  	user_service "code.gitea.io/gitea/services/user"
    22  )
    23  
    24  // Sync causes this ldap source to synchronize its users with the db
    25  func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
    26  	log.Trace("Doing: SyncExternalUsers[%s]", source.authSource.Name)
    27  
    28  	isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0
    29  	var sshKeysNeedUpdate bool
    30  
    31  	// Find all users with this login type - FIXME: Should this be an iterator?
    32  	users, err := user_model.GetUsersBySource(ctx, source.authSource)
    33  	if err != nil {
    34  		log.Error("SyncExternalUsers: %v", err)
    35  		return err
    36  	}
    37  	select {
    38  	case <-ctx.Done():
    39  		log.Warn("SyncExternalUsers: Cancelled before update of %s", source.authSource.Name)
    40  		return db.ErrCancelledf("Before update of %s", source.authSource.Name)
    41  	default:
    42  	}
    43  
    44  	usernameUsers := make(map[string]*user_model.User, len(users))
    45  	mailUsers := make(map[string]*user_model.User, len(users))
    46  	keepActiveUsers := make(container.Set[int64])
    47  
    48  	for _, u := range users {
    49  		usernameUsers[u.LowerName] = u
    50  		mailUsers[strings.ToLower(u.Email)] = u
    51  	}
    52  
    53  	sr, err := source.SearchEntries()
    54  	if err != nil {
    55  		log.Error("SyncExternalUsers LDAP source failure [%s], skipped", source.authSource.Name)
    56  		return nil
    57  	}
    58  
    59  	if len(sr) == 0 {
    60  		if !source.AllowDeactivateAll {
    61  			log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
    62  			return nil
    63  		}
    64  		log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
    65  	}
    66  
    67  	orgCache := make(map[string]*organization.Organization)
    68  	teamCache := make(map[string]*organization.Team)
    69  
    70  	groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	for _, su := range sr {
    76  		select {
    77  		case <-ctx.Done():
    78  			log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", source.authSource.Name)
    79  			// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
    80  			if sshKeysNeedUpdate {
    81  				err = asymkey_service.RewriteAllPublicKeys(ctx)
    82  				if err != nil {
    83  					log.Error("RewriteAllPublicKeys: %v", err)
    84  				}
    85  			}
    86  			return db.ErrCancelledf("During update of %s before completed update of users", source.authSource.Name)
    87  		default:
    88  		}
    89  		if len(su.Username) == 0 && len(su.Mail) == 0 {
    90  			continue
    91  		}
    92  
    93  		var usr *user_model.User
    94  		if len(su.Username) > 0 {
    95  			usr = usernameUsers[su.LowerName]
    96  		}
    97  		if usr == nil && len(su.Mail) > 0 {
    98  			usr = mailUsers[strings.ToLower(su.Mail)]
    99  		}
   100  
   101  		if usr != nil {
   102  			keepActiveUsers.Add(usr.ID)
   103  		} else if len(su.Username) == 0 {
   104  			// we cannot create the user if su.Username is empty
   105  			continue
   106  		}
   107  
   108  		if len(su.Mail) == 0 {
   109  			su.Mail = fmt.Sprintf("%s@localhost.local", su.Username)
   110  		}
   111  
   112  		fullName := composeFullName(su.Name, su.Surname, su.Username)
   113  		// If no existing user found, create one
   114  		if usr == nil {
   115  			log.Trace("SyncExternalUsers[%s]: Creating user %s", source.authSource.Name, su.Username)
   116  
   117  			usr = &user_model.User{
   118  				LowerName:   su.LowerName,
   119  				Name:        su.Username,
   120  				FullName:    fullName,
   121  				LoginType:   source.authSource.Type,
   122  				LoginSource: source.authSource.ID,
   123  				LoginName:   su.Username,
   124  				Email:       su.Mail,
   125  				IsAdmin:     su.IsAdmin,
   126  			}
   127  			overwriteDefault := &user_model.CreateUserOverwriteOptions{
   128  				IsRestricted: optional.Some(su.IsRestricted),
   129  				IsActive:     optional.Some(true),
   130  			}
   131  
   132  			err = user_model.CreateUser(ctx, usr, overwriteDefault)
   133  			if err != nil {
   134  				log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err)
   135  			}
   136  
   137  			if err == nil && isAttributeSSHPublicKeySet {
   138  				log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name)
   139  				if asymkey_model.AddPublicKeysBySource(ctx, usr, source.authSource, su.SSHPublicKey) {
   140  					sshKeysNeedUpdate = true
   141  				}
   142  			}
   143  
   144  			if err == nil && len(source.AttributeAvatar) > 0 {
   145  				_ = user_service.UploadAvatar(ctx, usr, su.Avatar)
   146  			}
   147  		} else if updateExisting {
   148  			// Synchronize SSH Public Key if that attribute is set
   149  			if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, usr, source.authSource, su.SSHPublicKey) {
   150  				sshKeysNeedUpdate = true
   151  			}
   152  
   153  			// Check if user data has changed
   154  			if (len(source.AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) ||
   155  				(len(source.RestrictedFilter) > 0 && usr.IsRestricted != su.IsRestricted) ||
   156  				!strings.EqualFold(usr.Email, su.Mail) ||
   157  				usr.FullName != fullName ||
   158  				!usr.IsActive {
   159  				log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name)
   160  
   161  				opts := &user_service.UpdateOptions{
   162  					FullName: optional.Some(fullName),
   163  					IsActive: optional.Some(true),
   164  				}
   165  				if source.AdminFilter != "" {
   166  					opts.IsAdmin = optional.Some(su.IsAdmin)
   167  				}
   168  				// Change existing restricted flag only if RestrictedFilter option is set
   169  				if !su.IsAdmin && source.RestrictedFilter != "" {
   170  					opts.IsRestricted = optional.Some(su.IsRestricted)
   171  				}
   172  
   173  				if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
   174  					log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err)
   175  				}
   176  
   177  				if err := user_service.ReplacePrimaryEmailAddress(ctx, usr, su.Mail); err != nil {
   178  					log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.authSource.Name, usr.Name, su.Mail, err)
   179  				}
   180  			}
   181  
   182  			if usr.IsUploadAvatarChanged(su.Avatar) {
   183  				if err == nil && len(source.AttributeAvatar) > 0 {
   184  					_ = user_service.UploadAvatar(ctx, usr, su.Avatar)
   185  				}
   186  			}
   187  		}
   188  		// Synchronize LDAP groups with organization and team memberships
   189  		if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) {
   190  			if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.Groups, groupTeamMapping, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil {
   191  				log.Error("SyncGroupsToTeamsCached: %v", err)
   192  			}
   193  		}
   194  	}
   195  
   196  	// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
   197  	if sshKeysNeedUpdate {
   198  		err = asymkey_service.RewriteAllPublicKeys(ctx)
   199  		if err != nil {
   200  			log.Error("RewriteAllPublicKeys: %v", err)
   201  		}
   202  	}
   203  
   204  	select {
   205  	case <-ctx.Done():
   206  		log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", source.authSource.Name)
   207  		return db.ErrCancelledf("During update of %s before delete users", source.authSource.Name)
   208  	default:
   209  	}
   210  
   211  	// Deactivate users not present in LDAP
   212  	if updateExisting {
   213  		for _, usr := range users {
   214  			if keepActiveUsers.Contains(usr.ID) {
   215  				continue
   216  			}
   217  
   218  			log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name)
   219  
   220  			opts := &user_service.UpdateOptions{
   221  				IsActive: optional.Some(false),
   222  			}
   223  			if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
   224  				log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err)
   225  			}
   226  		}
   227  	}
   228  	return nil
   229  }