mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-28 07:35:49 +00:00
fix(repo): centralize repository-scoped authorization (#39063)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -91,19 +91,6 @@ func (org *Organization) IsOwnedBy(ctx context.Context, uid int64) (bool, error)
|
||||
return IsOrganizationOwner(ctx, org.ID, uid)
|
||||
}
|
||||
|
||||
// CanChangeRepoTeamAccess reports whether a repository administrator can change team access.
|
||||
func (org *Organization) CanChangeRepoTeamAccess(ctx context.Context, doer *user_model.User) (bool, error) {
|
||||
if org.RepoAdminChangeTeamAccess || doer.IsAdmin {
|
||||
return true, nil
|
||||
}
|
||||
return org.IsOwnedBy(ctx, doer.ID)
|
||||
}
|
||||
|
||||
// IsOrgAdmin returns true if given user is in the owner team or an admin team.
|
||||
func (org *Organization) IsOrgAdmin(ctx context.Context, uid int64) (bool, error) {
|
||||
return IsOrganizationAdmin(ctx, org.ID, uid)
|
||||
}
|
||||
|
||||
// IsOrgMember returns true if given user is member of organization.
|
||||
func (org *Organization) IsOrgMember(ctx context.Context, uid int64) (bool, error) {
|
||||
return IsOrganizationMember(ctx, org.ID, uid)
|
||||
@@ -598,8 +585,8 @@ func RemoveOrgRepo(ctx context.Context, orgID, repoID int64) error {
|
||||
|
||||
// GetUserTeams returns all teams that belong to user,
|
||||
// and that the user has joined.
|
||||
func (org *Organization) GetUserTeams(ctx context.Context, userID int64, cols ...string) ([]*Team, error) {
|
||||
teams := make([]*Team, 0, org.NumTeams)
|
||||
func (org *Organization) GetUserTeams(ctx context.Context, userID int64, cols ...string) (TeamList, error) {
|
||||
teams := make(TeamList, 0, org.NumTeams)
|
||||
return teams, db.GetEngine(ctx).
|
||||
Where("`team_user`.org_id = ?", org.ID).
|
||||
Join("INNER", "team_user", "`team_user`.team_id = team.id").
|
||||
|
||||
@@ -69,20 +69,6 @@ func IsOrganizationOwner(ctx context.Context, orgID, uid int64) (bool, error) {
|
||||
return IsTeamMember(ctx, orgID, ownerTeam.ID, uid)
|
||||
}
|
||||
|
||||
// IsOrganizationAdmin returns true if given user is in the owner team or an admin team.
|
||||
func IsOrganizationAdmin(ctx context.Context, orgID, uid int64) (bool, error) {
|
||||
teams, err := GetUserOrgTeams(ctx, orgID, uid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, t := range teams {
|
||||
if t.HasAdminAccess() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsOrganizationMember returns true if given user is member of organization.
|
||||
func IsOrganizationMember(ctx context.Context, orgID, uid int64) (bool, error) {
|
||||
return db.GetEngine(ctx).
|
||||
|
||||
+19
-17
@@ -74,19 +74,25 @@ const OwnerTeamName = "Owners"
|
||||
|
||||
// Team represents a organization team.
|
||||
type Team struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
LowerName string
|
||||
Name string
|
||||
Description string
|
||||
AccessMode perm.AccessMode `xorm:"'authorize'"`
|
||||
Members []*user_model.User `xorm:"-"`
|
||||
NumRepos int
|
||||
NumMembers int
|
||||
Units []*TeamUnit `xorm:"-"`
|
||||
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
|
||||
CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 2"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
LowerName string
|
||||
Name string
|
||||
Description string
|
||||
AccessMode perm.AccessMode `xorm:"'authorize'"`
|
||||
Members []*user_model.User `xorm:"-"`
|
||||
NumRepos int
|
||||
NumMembers int
|
||||
Units []*TeamUnit `xorm:"-"`
|
||||
|
||||
// All repos in the org are included in this team automatically
|
||||
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
// Any user with CanCreateOrgRepo permission can create a repository in the organization, regardless of team membership.
|
||||
// And the user will become the repo's admin (via collaborator) after the creation.
|
||||
CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 2"`
|
||||
}
|
||||
|
||||
func (t *Team) IsPublic() bool { return t.Visibility.IsPublic() }
|
||||
@@ -176,10 +182,6 @@ func (t *Team) IsMember(ctx context.Context, userID int64) bool {
|
||||
return isMember
|
||||
}
|
||||
|
||||
func (t *Team) HasAdminAccess() bool {
|
||||
return t.AccessMode >= perm.AccessModeAdmin
|
||||
}
|
||||
|
||||
// LoadMembers returns paginated members in team of organization.
|
||||
func (t *Team) LoadMembers(ctx context.Context) (err error) {
|
||||
t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{
|
||||
|
||||
@@ -39,6 +39,18 @@ func (t TeamList) AnyRepoUnitMaxAccess(ctx context.Context, tp unit.Type) perm.A
|
||||
return maxAccess
|
||||
}
|
||||
|
||||
func (t TeamList) HasAllRepoAdminAccess() bool {
|
||||
for _, team := range t {
|
||||
if team.IsOwnerTeam() || team.AccessMode == perm.AccessModeOwner {
|
||||
return true
|
||||
}
|
||||
if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SearchTeamOptions holds the search options
|
||||
type SearchTeamOptions struct {
|
||||
db.ListOptions
|
||||
|
||||
@@ -33,7 +33,9 @@ func init() {
|
||||
db.RegisterModel(new(Access))
|
||||
}
|
||||
|
||||
func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm.AccessMode, error) {
|
||||
// modeByOwnerAndAccess returns the access mode of a user to a repository,
|
||||
// considering: the repository's owner (visibility and doer restriction), any explicit "access" records.
|
||||
func modeByOwnerAndAccess(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm.AccessMode, error) {
|
||||
mode := perm.AccessModeNone
|
||||
var userID int64
|
||||
restricted := false
|
||||
@@ -69,14 +71,6 @@ func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
|
||||
return a.Mode, nil
|
||||
}
|
||||
|
||||
func maxAccessMode(modes ...perm.AccessMode) perm.AccessMode {
|
||||
maxMode := perm.AccessModeNone
|
||||
for _, mode := range modes {
|
||||
maxMode = max(maxMode, mode)
|
||||
}
|
||||
return maxMode
|
||||
}
|
||||
|
||||
type userAccess struct {
|
||||
User *user_model.User
|
||||
Mode perm.AccessMode
|
||||
@@ -85,7 +79,7 @@ type userAccess struct {
|
||||
// updateUserAccess updates an access map so that user has at least mode
|
||||
func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mode perm.AccessMode) {
|
||||
if ua, ok := accessMap[user.ID]; ok {
|
||||
ua.Mode = maxAccessMode(ua.Mode, mode)
|
||||
ua.Mode = max(ua.Mode, mode)
|
||||
} else {
|
||||
accessMap[user.ID] = &userAccess{User: user, Mode: mode}
|
||||
}
|
||||
@@ -263,7 +257,7 @@ func RecalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid
|
||||
t.AccessMode = perm.AccessModeOwner
|
||||
}
|
||||
|
||||
accessMode = maxAccessMode(accessMode, t.AccessMode)
|
||||
accessMode = max(accessMode, t.AccessMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ type Permission struct {
|
||||
|
||||
everyoneAccessMode map[unit.Type]perm_model.AccessMode // the unit's minimal access mode for every signed-in user
|
||||
anonymousAccessMode map[unit.Type]perm_model.AccessMode // the unit's minimal access mode for anonymous (non-signed-in) user
|
||||
|
||||
orgRepoTeams []*organization.Team
|
||||
}
|
||||
|
||||
// IsOwner returns true if current user is the owner of repository.
|
||||
@@ -445,7 +447,7 @@ func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repos
|
||||
}
|
||||
|
||||
// plain user TODO: this check should be replaced, only need to check collaborator access mode
|
||||
perm.AccessMode, err = accessLevel(ctx, user, repo)
|
||||
perm.AccessMode, err = modeByOwnerAndAccess(ctx, user, repo)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
@@ -459,11 +461,11 @@ func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repos
|
||||
perm.AccessMode = max(perm.AccessMode, minAccessMode)
|
||||
|
||||
// get units mode from teams
|
||||
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
||||
perm.orgRepoTeams, err = organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
||||
if err != nil {
|
||||
return perm, err
|
||||
}
|
||||
if len(teams) == 0 {
|
||||
if len(perm.orgRepoTeams) == 0 {
|
||||
return perm, nil
|
||||
}
|
||||
|
||||
@@ -477,8 +479,8 @@ func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repos
|
||||
}
|
||||
|
||||
// if user in an owner team
|
||||
for _, team := range teams {
|
||||
if team.HasAdminAccess() {
|
||||
for _, team := range perm.orgRepoTeams {
|
||||
if team.IsOwnerTeam() || team.AccessMode == perm_model.AccessModeOwner {
|
||||
perm.AccessMode = perm_model.AccessModeOwner
|
||||
perm.unitsMode = nil
|
||||
return perm, nil
|
||||
@@ -486,7 +488,7 @@ func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repos
|
||||
}
|
||||
|
||||
for _, u := range repo.Units {
|
||||
for _, team := range teams {
|
||||
for _, team := range perm.orgRepoTeams {
|
||||
teamMode, _ := team.UnitAccessModeEx(ctx, u.Type)
|
||||
unitAccessMode := max(perm.unitsMode[u.Type], minAccessMode, teamMode)
|
||||
perm.unitsMode[u.Type] = unitAccessMode
|
||||
@@ -496,52 +498,35 @@ func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repos
|
||||
return perm, err
|
||||
}
|
||||
|
||||
// IsUserRealRepoAdmin check if this user is real repo admin
|
||||
func IsUserRealRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
|
||||
if repo.OwnerID == user.ID {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err := repo.LoadOwner(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
accessMode, err := accessLevel(ctx, user, repo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return accessMode >= perm_model.AccessModeAdmin, nil
|
||||
func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) bool {
|
||||
return (user != nil && user.IsAdmin) || IsUserRealRepoAdmin(ctx, repo, user)
|
||||
}
|
||||
|
||||
// IsUserRepoAdmin return true if user has admin right of a repo
|
||||
func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
|
||||
// IsUserRealRepoAdmin check if this user is real repo admin (but not a site admin who also has repo admin access)
|
||||
func IsUserRealRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) bool {
|
||||
if user == nil || repo == nil {
|
||||
return false, nil
|
||||
}
|
||||
if user.IsAdmin {
|
||||
return true, nil
|
||||
return false
|
||||
}
|
||||
|
||||
mode, err := accessLevel(ctx, user, repo)
|
||||
mode, err := modeByOwnerAndAccess(ctx, user, repo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
if mode >= perm_model.AccessModeAdmin {
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
|
||||
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false
|
||||
}
|
||||
|
||||
for _, team := range teams {
|
||||
if team.HasAdminAccess() {
|
||||
return true, nil
|
||||
if team.AccessMode >= perm_model.AccessModeAdmin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
||||
@@ -701,3 +686,39 @@ func CanReadWorkflowCrossRepo(ctx context.Context, targetRepo *repo_model.Reposi
|
||||
}
|
||||
return botPerm.AccessMode >= perm_model.AccessModeRead, nil
|
||||
}
|
||||
|
||||
func CanDoerManageRepoDangerZone(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, perm *Permission) bool {
|
||||
if perm.IsOwner() {
|
||||
return true
|
||||
}
|
||||
|
||||
// FIXME: ORG-REPO-ADMIN-DANGER-ZONE: this is the legacy logic, "org repo admin" can delete a repo
|
||||
// Ideally we need a new field in like "AdminManageDangerZone" to control this permission, but for now we keep the legacy logic
|
||||
for _, team := range perm.orgRepoTeams {
|
||||
if team.AccessMode >= perm_model.AccessModeAdmin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// A special case: if the team allows to create repo, then the doer will be added as a collaborator with admin access.
|
||||
// For this case, we also allow the doer to manage the danger zone as well, because the doer is effectively a repo admin.
|
||||
// Since the admin permission from team is already allowed above (legacy logic), here nothing worse.
|
||||
// Keep in mind: the newly created repo isn't in any org team, it only has the doer as a collaborator with admin access.
|
||||
// So we need to get all the teams of the doer to check.
|
||||
allowCreateRepo := false
|
||||
doerOrgTeams, _ := organization.GetUserOrgTeams(ctx, repo.OwnerID, doer.ID)
|
||||
for _, team := range doerOrgTeams {
|
||||
if allowCreateRepo = team.CanCreateOrgRepo; allowCreateRepo {
|
||||
break
|
||||
}
|
||||
}
|
||||
return allowCreateRepo && perm.IsAdmin()
|
||||
}
|
||||
|
||||
func CanDoerManageOrgRepoCollaboratorTeam(ctx context.Context, repo *repo_model.Repository, perm *Permission) bool {
|
||||
_ = repo.LoadOwner(ctx)
|
||||
if repo.Owner == nil || !repo.Owner.IsOrganization() {
|
||||
return false
|
||||
}
|
||||
return perm.IsOwner() || perm.IsAdmin() && repo.Owner.RepoAdminChangeTeamAccess
|
||||
}
|
||||
|
||||
+10
-4
@@ -144,10 +144,16 @@ type User struct {
|
||||
NumRepos int
|
||||
|
||||
// For organization
|
||||
NumTeams int
|
||||
NumMembers int
|
||||
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
|
||||
RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
|
||||
NumTeams int
|
||||
NumMembers int
|
||||
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
||||
// Introduced by "Add teams to repo on collaboration page. (#8045)"
|
||||
// Whether a repo admin can add/remove a team to/from the repo on the collaboration page
|
||||
RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
// FIXME: ORG-REPO-ADMIN-DANGER-ZONE: it needs a new field to decide whether a repo admin can manage the repo's danger zone
|
||||
// Team won't work for this case, because a newly create org repo isn't in any team (same as above)
|
||||
|
||||
// Preferences
|
||||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
||||
|
||||
Reference in New Issue
Block a user