diff options
Diffstat (limited to 'models/perm/access')
-rw-r--r-- | models/perm/access/repo_permission.go | 109 | ||||
-rw-r--r-- | models/perm/access/repo_permission_test.go | 68 |
2 files changed, 138 insertions, 39 deletions
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 5e7ecb31ea..7de43ecd07 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -25,7 +26,8 @@ type Permission struct { units []*repo_model.RepoUnit unitsMode map[unit.Type]perm_model.AccessMode - everyoneAccessMode map[unit.Type]perm_model.AccessMode + 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 } // IsOwner returns true if current user is the owner of repository. @@ -39,7 +41,8 @@ func (p *Permission) IsAdmin() bool { } // HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository. -// It doesn't count the "everyone access mode". +// It doesn't count the "public(anonymous/everyone) access mode". +// TODO: most calls to this function should be replaced with `HasAnyUnitAccessOrPublicAccess` func (p *Permission) HasAnyUnitAccess() bool { for _, v := range p.unitsMode { if v >= perm_model.AccessModeRead { @@ -49,13 +52,22 @@ func (p *Permission) HasAnyUnitAccess() bool { return p.AccessMode >= perm_model.AccessModeRead } -func (p *Permission) HasAnyUnitAccessOrEveryoneAccess() bool { +func (p *Permission) HasAnyUnitPublicAccess() bool { + for _, v := range p.anonymousAccessMode { + if v >= perm_model.AccessModeRead { + return true + } + } for _, v := range p.everyoneAccessMode { if v >= perm_model.AccessModeRead { return true } } - return p.HasAnyUnitAccess() + return false +} + +func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool { + return p.HasAnyUnitPublicAccess() || p.HasAnyUnitAccess() } // HasUnits returns true if the permission contains attached units @@ -73,14 +85,16 @@ func (p *Permission) GetFirstUnitRepoID() int64 { } // UnitAccessMode returns current user access mode to the specify unit of the repository -// It also considers "everyone access mode" +// It also considers "public (anonymous/everyone) access mode" func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode { // if the units map contains the access mode, use it, but admin/owner mode could override it if m, ok := p.unitsMode[unitType]; ok { return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m) } // if the units map does not contain the access mode, return the default access mode if the unit exists - unitDefaultAccessMode := max(p.AccessMode, p.everyoneAccessMode[unitType]) + unitDefaultAccessMode := p.AccessMode + unitDefaultAccessMode = max(unitDefaultAccessMode, p.anonymousAccessMode[unitType]) + unitDefaultAccessMode = max(unitDefaultAccessMode, p.everyoneAccessMode[unitType]) hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType }) return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone) } @@ -171,27 +185,41 @@ func (p *Permission) LogString() string { format += "\n\tunitsMode[%-v]: %-v" args = append(args, key.LogString(), value.LogString()) } + format += "\n\tanonymousAccessMode: %-v" + args = append(args, p.anonymousAccessMode) format += "\n\teveryoneAccessMode: %-v" args = append(args, p.everyoneAccessMode) format += "\n\t]>" return fmt.Sprintf(format, args...) } +func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) { + if setting.Repository.ForcePrivate { + return + } + if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] { + if *modeMap == nil { + *modeMap = make(map[unit.Type]perm_model.AccessMode) + } + (*modeMap)[unitType] = accessMode + } +} + func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) { + // apply public (anonymous) access permissions + for _, u := range perm.units { + applyPublicAccessPermission(u.Type, u.AnonymousAccessMode, &perm.anonymousAccessMode) + } + if user == nil || user.ID <= 0 { // for anonymous access, it could be: // AccessMode is None or Read, units has repo units, unitModes is nil return } - // apply everyone access permissions + // apply public (everyone) access permissions for _, u := range perm.units { - if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] { - if perm.everyoneAccessMode == nil { - perm.everyoneAccessMode = make(map[unit.Type]perm_model.AccessMode) - } - perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode - } + applyPublicAccessPermission(u.Type, u.EveryoneAccessMode, &perm.everyoneAccessMode) } if perm.unitsMode == nil { @@ -209,6 +237,11 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) { break } } + for t := range perm.anonymousAccessMode { + if shouldKeep = shouldKeep || u.Type == t; shouldKeep { + break + } + } for t := range perm.everyoneAccessMode { if shouldKeep = shouldKeep || u.Type == t; shouldKeep { break @@ -235,7 +268,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use perm.units = repo.Units // anonymous user visit private repo. - // TODO: anonymous user visit public unit of private repo??? if user == nil && repo.IsPrivate { perm.AccessMode = perm_model.AccessModeNone return perm, nil @@ -254,7 +286,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } // Prevent strangers from checking out public repo of private organization/users - // 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 + // Allow user if they are a collaborator of a repo within a private user or a private organization but not a member of the organization itself + // TODO: rename it to "IsOwnerVisibleToDoer" if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator { perm.AccessMode = perm_model.AccessModeNone return perm, nil @@ -272,7 +305,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use return perm, nil } - // plain user + // plain user TODO: this check should be replaced, only need to check collaborator access mode perm.AccessMode, err = accessLevel(ctx, user, repo) if err != nil { return perm, err @@ -282,6 +315,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use return perm, nil } + // now: the owner is visible to doer, if the repo is public, then the min access mode is read + minAccessMode := util.Iif(!repo.IsPrivate && !user.IsRestricted, perm_model.AccessModeRead, perm_model.AccessModeNone) + perm.AccessMode = max(perm.AccessMode, minAccessMode) + + // get units mode from teams + teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID) + if err != nil { + return perm, err + } + if len(teams) == 0 { + return perm, nil + } + perm.unitsMode = make(map[unit.Type]perm_model.AccessMode) // Collaborators on organization @@ -291,15 +337,9 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } } - // get units mode from teams - teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID) - if err != nil { - return perm, err - } - // if user in an owner team for _, team := range teams { - if team.AccessMode >= perm_model.AccessModeAdmin { + if team.HasAdminAccess() { perm.AccessMode = perm_model.AccessModeOwner perm.unitsMode = nil return perm, nil @@ -307,19 +347,12 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } for _, u := range repo.Units { - var found bool for _, team := range teams { + unitAccessMode := minAccessMode if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist { - perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode) - found = true - } - } - - // for a public repo on an organization, a non-restricted user has read permission on non-team defined units. - if !found && !repo.IsPrivate && !user.IsRestricted { - if _, ok := perm.unitsMode[u.Type]; !ok { - perm.unitsMode[u.Type] = perm_model.AccessModeRead + unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode) } + perm.unitsMode[u.Type] = unitAccessMode } } @@ -367,7 +400,7 @@ func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *use } for _, team := range teams { - if team.AccessMode >= perm_model.AccessModeAdmin { + if team.HasAdminAccess() { return true, nil } } @@ -376,13 +409,13 @@ func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *use // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the // user does not have access. -func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint +func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint:revive // export stutter return AccessLevelUnit(ctx, user, repo, unit.TypeCode) } // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the // user does not have access. -func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint +func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint:revive // export stutter perm, err := GetUserRepoPermission(ctx, repo, user) if err != nil { return perm_model.AccessModeNone, err @@ -490,3 +523,7 @@ func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u return perm.CanRead(unitType) } + +func PermissionNoAccess() Permission { + return Permission{AccessMode: perm_model.AccessModeNone} +} diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go index 9862da0673..c8675b1ded 100644 --- a/models/perm/access/repo_permission_test.go +++ b/models/perm/access/repo_permission_test.go @@ -6,12 +6,16 @@ package access import ( "testing" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" perm_model "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHasAnyUnitAccess(t *testing.T) { @@ -22,14 +26,21 @@ func TestHasAnyUnitAccess(t *testing.T) { units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}}, } assert.False(t, perm.HasAnyUnitAccess()) - assert.False(t, perm.HasAnyUnitAccessOrEveryoneAccess()) + assert.False(t, perm.HasAnyUnitAccessOrPublicAccess()) perm = Permission{ units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}}, everyoneAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead}, } assert.False(t, perm.HasAnyUnitAccess()) - assert.True(t, perm.HasAnyUnitAccessOrEveryoneAccess()) + assert.True(t, perm.HasAnyUnitAccessOrPublicAccess()) + + perm = Permission{ + units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}}, + anonymousAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead}, + } + assert.False(t, perm.HasAnyUnitAccess()) + assert.True(t, perm.HasAnyUnitAccessOrPublicAccess()) perm = Permission{ AccessMode: perm_model.AccessModeRead, @@ -43,7 +54,7 @@ func TestHasAnyUnitAccess(t *testing.T) { assert.True(t, perm.HasAnyUnitAccess()) } -func TestApplyEveryoneRepoPermission(t *testing.T) { +func TestApplyPublicAccessRepoPermission(t *testing.T) { perm := Permission{ AccessMode: perm_model.AccessModeNone, units: []*repo_model.RepoUnit{ @@ -56,6 +67,15 @@ func TestApplyEveryoneRepoPermission(t *testing.T) { perm = Permission{ AccessMode: perm_model.AccessModeNone, units: []*repo_model.RepoUnit{ + {Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead}, + }, + } + finalProcessRepoUnitPermission(nil, &perm) + assert.True(t, perm.CanRead(unit.TypeWiki)) + + perm = Permission{ + AccessMode: perm_model.AccessModeNone, + units: []*repo_model.RepoUnit{ {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, }, } @@ -136,3 +156,45 @@ func TestUnitAccessMode(t *testing.T) { } assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map") } + +func TestGetUserRepoPermission(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo + require.NoError(t, repo32.LoadOwner(ctx)) + require.True(t, repo32.Owner.IsOrganization()) + + require.NoError(t, db.TruncateBeans(ctx, &organization.Team{}, &organization.TeamUser{}, &organization.TeamRepo{}, &organization.TeamUnit{})) + org := repo32.Owner + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + team := &organization.Team{OrgID: org.ID, LowerName: "test_team"} + require.NoError(t, db.Insert(ctx, team)) + + t.Run("DoerInTeamWithNoRepo", func(t *testing.T) { + require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID})) + perm, err := GetUserRepoPermission(ctx, repo32, user) + require.NoError(t, err) + assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) + assert.Nil(t, perm.unitsMode) // doer in the team, but has no access to the repo + }) + + require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo32.ID})) + require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone})) + t.Run("DoerWithTeamUnitAccessNone", func(t *testing.T) { + perm, err := GetUserRepoPermission(ctx, repo32, user) + require.NoError(t, err) + assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) + assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeCode]) + assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues]) + }) + + require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{})) + require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeWrite})) + t.Run("DoerWithTeamUnitAccessWrite", func(t *testing.T) { + perm, err := GetUserRepoPermission(ctx, repo32, user) + require.NoError(t, err) + assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) + assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode]) + assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues]) + }) +} |