code.gitea.io/gitea@v1.21.7/services/auth/source/source_group_sync.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package source
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"code.gitea.io/gitea/models"
    11  	"code.gitea.io/gitea/models/organization"
    12  	user_model "code.gitea.io/gitea/models/user"
    13  	"code.gitea.io/gitea/modules/container"
    14  	"code.gitea.io/gitea/modules/log"
    15  )
    16  
    17  type syncType int
    18  
    19  const (
    20  	syncAdd syncType = iota
    21  	syncRemove
    22  )
    23  
    24  // SyncGroupsToTeams maps authentication source groups to organization and team memberships
    25  func SyncGroupsToTeams(ctx context.Context, user *user_model.User, sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string, performRemoval bool) error {
    26  	orgCache := make(map[string]*organization.Organization)
    27  	teamCache := make(map[string]*organization.Team)
    28  	return SyncGroupsToTeamsCached(ctx, user, sourceUserGroups, sourceGroupTeamMapping, performRemoval, orgCache, teamCache)
    29  }
    30  
    31  // SyncGroupsToTeamsCached maps authentication source groups to organization and team memberships
    32  func SyncGroupsToTeamsCached(ctx context.Context, user *user_model.User, sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string, performRemoval bool, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error {
    33  	membershipsToAdd, membershipsToRemove := resolveMappedMemberships(sourceUserGroups, sourceGroupTeamMapping)
    34  
    35  	if performRemoval {
    36  		if err := syncGroupsToTeamsCached(ctx, user, membershipsToRemove, syncRemove, orgCache, teamCache); err != nil {
    37  			return fmt.Errorf("could not sync[remove] user groups: %w", err)
    38  		}
    39  	}
    40  
    41  	if err := syncGroupsToTeamsCached(ctx, user, membershipsToAdd, syncAdd, orgCache, teamCache); err != nil {
    42  		return fmt.Errorf("could not sync[add] user groups: %w", err)
    43  	}
    44  
    45  	return nil
    46  }
    47  
    48  func resolveMappedMemberships(sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string) (map[string][]string, map[string][]string) {
    49  	membershipsToAdd := map[string][]string{}
    50  	membershipsToRemove := map[string][]string{}
    51  	for group, memberships := range sourceGroupTeamMapping {
    52  		isUserInGroup := sourceUserGroups.Contains(group)
    53  		if isUserInGroup {
    54  			for org, teams := range memberships {
    55  				membershipsToAdd[org] = append(membershipsToAdd[org], teams...)
    56  			}
    57  		} else {
    58  			for org, teams := range memberships {
    59  				membershipsToRemove[org] = append(membershipsToRemove[org], teams...)
    60  			}
    61  		}
    62  	}
    63  	return membershipsToAdd, membershipsToRemove
    64  }
    65  
    66  func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeamMap map[string][]string, action syncType, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error {
    67  	for orgName, teamNames := range orgTeamMap {
    68  		var err error
    69  		org, ok := orgCache[orgName]
    70  		if !ok {
    71  			org, err = organization.GetOrgByName(ctx, orgName)
    72  			if err != nil {
    73  				if organization.IsErrOrgNotExist(err) {
    74  					// organization must be created before group sync
    75  					log.Warn("group sync: Could not find organisation %s: %v", orgName, err)
    76  					continue
    77  				}
    78  				return err
    79  			}
    80  			orgCache[orgName] = org
    81  		}
    82  		for _, teamName := range teamNames {
    83  			team, ok := teamCache[orgName+teamName]
    84  			if !ok {
    85  				team, err = org.GetTeam(ctx, teamName)
    86  				if err != nil {
    87  					if organization.IsErrTeamNotExist(err) {
    88  						// team must be created before group sync
    89  						log.Warn("group sync: Could not find team %s: %v", teamName, err)
    90  						continue
    91  					}
    92  					return err
    93  				}
    94  				teamCache[orgName+teamName] = team
    95  			}
    96  
    97  			isMember, err := organization.IsTeamMember(ctx, org.ID, team.ID, user.ID)
    98  			if err != nil {
    99  				return err
   100  			}
   101  
   102  			if action == syncAdd && !isMember {
   103  				if err := models.AddTeamMember(ctx, team, user.ID); err != nil {
   104  					log.Error("group sync: Could not add user to team: %v", err)
   105  					return err
   106  				}
   107  			} else if action == syncRemove && isMember {
   108  				if err := models.RemoveTeamMember(ctx, team, user.ID); err != nil {
   109  					log.Error("group sync: Could not remove user from team: %v", err)
   110  					return err
   111  				}
   112  			}
   113  		}
   114  	}
   115  	return nil
   116  }