code.gitea.io/gitea@v1.21.7/models/perm/access/repo_permission.go (about)

     1  // Copyright 2018 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package access
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"code.gitea.io/gitea/models/db"
    11  	"code.gitea.io/gitea/models/organization"
    12  	perm_model "code.gitea.io/gitea/models/perm"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	"code.gitea.io/gitea/models/unit"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/log"
    17  )
    18  
    19  // Permission contains all the permissions related variables to a repository for a user
    20  type Permission struct {
    21  	AccessMode perm_model.AccessMode
    22  	Units      []*repo_model.RepoUnit
    23  	UnitsMode  map[unit.Type]perm_model.AccessMode
    24  }
    25  
    26  // IsOwner returns true if current user is the owner of repository.
    27  func (p *Permission) IsOwner() bool {
    28  	return p.AccessMode >= perm_model.AccessModeOwner
    29  }
    30  
    31  // IsAdmin returns true if current user has admin or higher access of repository.
    32  func (p *Permission) IsAdmin() bool {
    33  	return p.AccessMode >= perm_model.AccessModeAdmin
    34  }
    35  
    36  // HasAccess returns true if the current user has at least read access to any unit of this repository
    37  func (p *Permission) HasAccess() bool {
    38  	if p.UnitsMode == nil {
    39  		return p.AccessMode >= perm_model.AccessModeRead
    40  	}
    41  	return len(p.UnitsMode) > 0
    42  }
    43  
    44  // UnitAccessMode returns current user accessmode to the specify unit of the repository
    45  func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
    46  	if p.UnitsMode == nil {
    47  		for _, u := range p.Units {
    48  			if u.Type == unitType {
    49  				return p.AccessMode
    50  			}
    51  		}
    52  		return perm_model.AccessModeNone
    53  	}
    54  	return p.UnitsMode[unitType]
    55  }
    56  
    57  // CanAccess returns true if user has mode access to the unit of the repository
    58  func (p *Permission) CanAccess(mode perm_model.AccessMode, unitType unit.Type) bool {
    59  	return p.UnitAccessMode(unitType) >= mode
    60  }
    61  
    62  // CanAccessAny returns true if user has mode access to any of the units of the repository
    63  func (p *Permission) CanAccessAny(mode perm_model.AccessMode, unitTypes ...unit.Type) bool {
    64  	for _, u := range unitTypes {
    65  		if p.CanAccess(mode, u) {
    66  			return true
    67  		}
    68  	}
    69  	return false
    70  }
    71  
    72  // CanRead returns true if user could read to this unit
    73  func (p *Permission) CanRead(unitType unit.Type) bool {
    74  	return p.CanAccess(perm_model.AccessModeRead, unitType)
    75  }
    76  
    77  // CanReadAny returns true if user has read access to any of the units of the repository
    78  func (p *Permission) CanReadAny(unitTypes ...unit.Type) bool {
    79  	return p.CanAccessAny(perm_model.AccessModeRead, unitTypes...)
    80  }
    81  
    82  // CanReadIssuesOrPulls returns true if isPull is true and user could read pull requests and
    83  // returns true if isPull is false and user could read to issues
    84  func (p *Permission) CanReadIssuesOrPulls(isPull bool) bool {
    85  	if isPull {
    86  		return p.CanRead(unit.TypePullRequests)
    87  	}
    88  	return p.CanRead(unit.TypeIssues)
    89  }
    90  
    91  // CanWrite returns true if user could write to this unit
    92  func (p *Permission) CanWrite(unitType unit.Type) bool {
    93  	return p.CanAccess(perm_model.AccessModeWrite, unitType)
    94  }
    95  
    96  // CanWriteIssuesOrPulls returns true if isPull is true and user could write to pull requests and
    97  // returns true if isPull is false and user could write to issues
    98  func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
    99  	if isPull {
   100  		return p.CanWrite(unit.TypePullRequests)
   101  	}
   102  	return p.CanWrite(unit.TypeIssues)
   103  }
   104  
   105  func (p *Permission) LogString() string {
   106  	format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
   107  	args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)}
   108  
   109  	for i, unit := range p.Units {
   110  		config := ""
   111  		if unit.Config != nil {
   112  			configBytes, err := unit.Config.ToDB()
   113  			config = string(configBytes)
   114  			if err != nil {
   115  				config = err.Error()
   116  			}
   117  		}
   118  		format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
   119  		args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config)
   120  	}
   121  	for key, value := range p.UnitsMode {
   122  		format += "\nUnitMode[%-v]: %-v"
   123  		args = append(args, key.LogString(), value.LogString())
   124  	}
   125  	format += " ]>"
   126  	return fmt.Sprintf(format, args...)
   127  }
   128  
   129  // GetUserRepoPermission returns the user permissions to the repository
   130  func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) {
   131  	var perm Permission
   132  	if log.IsTrace() {
   133  		defer func() {
   134  			if user == nil {
   135  				log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v",
   136  					repo,
   137  					perm)
   138  				return
   139  			}
   140  			log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v",
   141  				user,
   142  				repo,
   143  				perm)
   144  		}()
   145  	}
   146  
   147  	// anonymous user visit private repo.
   148  	// TODO: anonymous user visit public unit of private repo???
   149  	if user == nil && repo.IsPrivate {
   150  		perm.AccessMode = perm_model.AccessModeNone
   151  		return perm, nil
   152  	}
   153  
   154  	var isCollaborator bool
   155  	var err error
   156  	if user != nil {
   157  		isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
   158  		if err != nil {
   159  			return perm, err
   160  		}
   161  	}
   162  
   163  	if err := repo.LoadOwner(ctx); err != nil {
   164  		return perm, err
   165  	}
   166  
   167  	// Prevent strangers from checking out public repo of private organization/users
   168  	// Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
   169  	if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator {
   170  		perm.AccessMode = perm_model.AccessModeNone
   171  		return perm, nil
   172  	}
   173  
   174  	if err := repo.LoadUnits(ctx); err != nil {
   175  		return perm, err
   176  	}
   177  
   178  	perm.Units = repo.Units
   179  
   180  	// anonymous visit public repo
   181  	if user == nil {
   182  		perm.AccessMode = perm_model.AccessModeRead
   183  		return perm, nil
   184  	}
   185  
   186  	// Admin or the owner has super access to the repository
   187  	if user.IsAdmin || user.ID == repo.OwnerID {
   188  		perm.AccessMode = perm_model.AccessModeOwner
   189  		return perm, nil
   190  	}
   191  
   192  	// plain user
   193  	perm.AccessMode, err = accessLevel(ctx, user, repo)
   194  	if err != nil {
   195  		return perm, err
   196  	}
   197  
   198  	if err := repo.LoadOwner(ctx); err != nil {
   199  		return perm, err
   200  	}
   201  	if !repo.Owner.IsOrganization() {
   202  		return perm, nil
   203  	}
   204  
   205  	perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
   206  
   207  	// Collaborators on organization
   208  	if isCollaborator {
   209  		for _, u := range repo.Units {
   210  			perm.UnitsMode[u.Type] = perm.AccessMode
   211  		}
   212  	}
   213  
   214  	// get units mode from teams
   215  	teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
   216  	if err != nil {
   217  		return perm, err
   218  	}
   219  
   220  	// if user in an owner team
   221  	for _, team := range teams {
   222  		if team.AccessMode >= perm_model.AccessModeAdmin {
   223  			perm.AccessMode = perm_model.AccessModeOwner
   224  			perm.UnitsMode = nil
   225  			return perm, nil
   226  		}
   227  	}
   228  
   229  	for _, u := range repo.Units {
   230  		var found bool
   231  		for _, team := range teams {
   232  			teamMode := team.UnitAccessMode(ctx, u.Type)
   233  			if teamMode > perm_model.AccessModeNone {
   234  				m := perm.UnitsMode[u.Type]
   235  				if m < teamMode {
   236  					perm.UnitsMode[u.Type] = teamMode
   237  				}
   238  				found = true
   239  			}
   240  		}
   241  
   242  		// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
   243  		if !found && !repo.IsPrivate && !user.IsRestricted {
   244  			if _, ok := perm.UnitsMode[u.Type]; !ok {
   245  				perm.UnitsMode[u.Type] = perm_model.AccessModeRead
   246  			}
   247  		}
   248  	}
   249  
   250  	// remove no permission units
   251  	perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
   252  	for t := range perm.UnitsMode {
   253  		for _, u := range repo.Units {
   254  			if u.Type == t {
   255  				perm.Units = append(perm.Units, u)
   256  			}
   257  		}
   258  	}
   259  
   260  	return perm, err
   261  }
   262  
   263  // IsUserRealRepoAdmin check if this user is real repo admin
   264  func IsUserRealRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) {
   265  	if repo.OwnerID == user.ID {
   266  		return true, nil
   267  	}
   268  
   269  	if err := repo.LoadOwner(db.DefaultContext); err != nil {
   270  		return false, err
   271  	}
   272  
   273  	accessMode, err := accessLevel(db.DefaultContext, user, repo)
   274  	if err != nil {
   275  		return false, err
   276  	}
   277  
   278  	return accessMode >= perm_model.AccessModeAdmin, nil
   279  }
   280  
   281  // IsUserRepoAdmin return true if user has admin right of a repo
   282  func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
   283  	if user == nil || repo == nil {
   284  		return false, nil
   285  	}
   286  	if user.IsAdmin {
   287  		return true, nil
   288  	}
   289  
   290  	mode, err := accessLevel(ctx, user, repo)
   291  	if err != nil {
   292  		return false, err
   293  	}
   294  	if mode >= perm_model.AccessModeAdmin {
   295  		return true, nil
   296  	}
   297  
   298  	teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
   299  	if err != nil {
   300  		return false, err
   301  	}
   302  
   303  	for _, team := range teams {
   304  		if team.AccessMode >= perm_model.AccessModeAdmin {
   305  			return true, nil
   306  		}
   307  	}
   308  	return false, nil
   309  }
   310  
   311  // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
   312  // user does not have access.
   313  func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint
   314  	return AccessLevelUnit(ctx, user, repo, unit.TypeCode)
   315  }
   316  
   317  // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
   318  // user does not have access.
   319  func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint
   320  	perm, err := GetUserRepoPermission(ctx, repo, user)
   321  	if err != nil {
   322  		return perm_model.AccessModeNone, err
   323  	}
   324  	return perm.UnitAccessMode(unitType), nil
   325  }
   326  
   327  // HasAccessUnit returns true if user has testMode to the unit of the repository
   328  func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) {
   329  	mode, err := AccessLevelUnit(ctx, user, repo, unitType)
   330  	return testMode <= mode, err
   331  }
   332  
   333  // CanBeAssigned return true if user can be assigned to issue or pull requests in repo
   334  // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
   335  func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
   336  	if user.IsOrganization() {
   337  		return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
   338  	}
   339  	perm, err := GetUserRepoPermission(ctx, repo, user)
   340  	if err != nil {
   341  		return false, err
   342  	}
   343  	return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) ||
   344  		perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
   345  }
   346  
   347  // HasAccess returns true if user has access to repo
   348  func HasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
   349  	var user *user_model.User
   350  	var err error
   351  	if userID > 0 {
   352  		user, err = user_model.GetUserByID(ctx, userID)
   353  		if err != nil {
   354  			return false, err
   355  		}
   356  	}
   357  	perm, err := GetUserRepoPermission(ctx, repo, user)
   358  	if err != nil {
   359  		return false, err
   360  	}
   361  	return perm.HasAccess(), nil
   362  }
   363  
   364  // getUsersWithAccessMode returns users that have at least given access mode to the repository.
   365  func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
   366  	if err = repo.LoadOwner(ctx); err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	e := db.GetEngine(ctx)
   371  	accesses := make([]*Access, 0, 10)
   372  	if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	// Leave a seat for owner itself to append later, but if owner is an organization
   377  	// and just waste 1 unit is cheaper than re-allocate memory once.
   378  	users := make([]*user_model.User, 0, len(accesses)+1)
   379  	if len(accesses) > 0 {
   380  		userIDs := make([]int64, len(accesses))
   381  		for i := 0; i < len(accesses); i++ {
   382  			userIDs[i] = accesses[i].UserID
   383  		}
   384  
   385  		if err = e.In("id", userIDs).Find(&users); err != nil {
   386  			return nil, err
   387  		}
   388  	}
   389  	if !repo.Owner.IsOrganization() {
   390  		users = append(users, repo.Owner)
   391  	}
   392  
   393  	return users, nil
   394  }
   395  
   396  // GetRepoReaders returns all users that have explicit read access or higher to the repository.
   397  func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) {
   398  	return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeRead)
   399  }
   400  
   401  // GetRepoWriters returns all users that have write access to the repository.
   402  func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err error) {
   403  	return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeWrite)
   404  }
   405  
   406  // IsRepoReader returns true if user has explicit read access or higher to the repository.
   407  func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
   408  	if repo.OwnerID == userID {
   409  		return true, nil
   410  	}
   411  	return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
   412  }
   413  
   414  // CheckRepoUnitUser check whether user could visit the unit of this repository
   415  func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
   416  	if user != nil && user.IsAdmin {
   417  		return true
   418  	}
   419  	perm, err := GetUserRepoPermission(ctx, repo, user)
   420  	if err != nil {
   421  		log.Error("GetUserRepoPermission: %w", err)
   422  		return false
   423  	}
   424  
   425  	return perm.CanRead(unitType)
   426  }