feat: vendor gitea 1.16.2

This commit is contained in:
2026-06-02 08:36:01 +00:00
parent 247cd60f69
commit 54798b4615
2145 changed files with 162965 additions and 126447 deletions
@@ -91,43 +91,80 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo
}
}
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
// refreshAccesses updates the repository's access records in the database by comparing the provided accessMap
// with existing records. It minimizes DB operations by performing selective inserts, updates, and deletes
// instead of removing all existing records and re-adding them.
func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) {
minMode := perm.AccessModeRead
minModeToKeep := perm.AccessModeRead
if err := repo.LoadOwner(ctx); err != nil {
return fmt.Errorf("LoadOwner: %w", err)
}
// If the repo isn't private and isn't owned by a organization,
// If the repo isn't private and isn't owned by an organization,
// increase the minMode to Write.
if !repo.IsPrivate && !repo.Owner.IsOrganization() {
minMode = perm.AccessModeWrite
minModeToKeep = perm.AccessModeWrite
}
newAccesses := make([]Access, 0, len(accessMap))
// Query existing accesses for cross-comparison
var existingAccesses []Access
if err := db.GetEngine(ctx).Where(builder.Eq{"repo_id": repo.ID}).Find(&existingAccesses); err != nil {
return fmt.Errorf("find existing accesses: %w", err)
}
existingMap := make(map[int64]perm.AccessMode, len(existingAccesses))
for _, a := range existingAccesses {
existingMap[a.UserID] = a.Mode
}
var toDelete []int64
var toInsert, toUpdate []Access
// Determine changes
for userID, ua := range accessMap {
if ua.Mode < minMode && !ua.User.IsRestricted {
continue
if ua.Mode < minModeToKeep && !ua.User.IsRestricted {
// No explicit access record needed (handled by default permissions, e.g., public repo access)
if _, exists := existingMap[userID]; exists {
toDelete = append(toDelete, userID)
}
} else {
desiredMode := ua.Mode
if existingMode, exists := existingMap[userID]; exists {
if existingMode != desiredMode {
toUpdate = append(toUpdate, Access{UserID: userID, RepoID: repo.ID, Mode: desiredMode})
}
} else {
toInsert = append(toInsert, Access{UserID: userID, RepoID: repo.ID, Mode: desiredMode})
}
}
newAccesses = append(newAccesses, Access{
UserID: userID,
RepoID: repo.ID,
Mode: ua.Mode,
})
delete(existingMap, userID)
}
// Delete old accesses and insert new ones for repository.
if _, err = db.DeleteByBean(ctx, &Access{RepoID: repo.ID}); err != nil {
return fmt.Errorf("delete old accesses: %w", err)
}
if len(newAccesses) == 0 {
return nil
// Remaining in existingMap should be deleted
for userID := range existingMap {
toDelete = append(toDelete, userID)
}
if err = db.Insert(ctx, newAccesses); err != nil {
return fmt.Errorf("insert new accesses: %w", err)
// Execute deletions
if len(toDelete) > 0 {
if _, err = db.GetEngine(ctx).In("user_id", toDelete).And("repo_id = ?", repo.ID).Delete(&Access{}); err != nil {
return fmt.Errorf("delete accesses: %w", err)
}
}
// Execute updates
for _, u := range toUpdate {
if _, err = db.GetEngine(ctx).Where("user_id = ? AND repo_id = ?", u.UserID, repo.ID).Cols("mode").Update(&Access{Mode: u.Mode}); err != nil {
return fmt.Errorf("update access for user %d: %w", u.UserID, err)
}
}
// Execute insertions
if len(toInsert) > 0 {
if err = db.Insert(ctx, toInsert); err != nil {
return fmt.Errorf("insert new accesses: %w", err)
}
}
return nil
}
@@ -15,11 +15,20 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAccessLevel(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func TestAccess(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("AccessLevel", testAccessLevel)
t.Run("HasAccess", testHasAccess)
t.Run("RecalculateAccesses", testRecalculateAccesses)
t.Run("RecalculateAccesses2", testRecalculateAccesses2)
t.Run("RecalculateAccessesUpdateMode", testRecalculateAccessesUpdateMode)
t.Run("RecalculateAccessesRemoveAccess", testRecalculateAccessesRemoveAccess)
}
func testAccessLevel(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
@@ -75,9 +84,7 @@ func TestAccessLevel(t *testing.T) {
assert.Equal(t, perm_model.AccessModeRead, level)
}
func TestHasAccess(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func testHasAccess(t *testing.T) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
// A public repository owned by User 2
@@ -101,9 +108,8 @@ func TestHasAccess(t *testing.T) {
assert.NoError(t, err)
}
func TestRepository_RecalculateAccesses(t *testing.T) {
func testRecalculateAccesses(t *testing.T) {
// test with organization repo
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
assert.NoError(t, repo1.LoadOwner(t.Context()))
@@ -118,9 +124,8 @@ func TestRepository_RecalculateAccesses(t *testing.T) {
assert.Equal(t, perm_model.AccessModeOwner, access.Mode)
}
func TestRepository_RecalculateAccesses2(t *testing.T) {
func testRecalculateAccesses2(t *testing.T) {
// test with non-organization repo
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.NoError(t, repo1.LoadOwner(t.Context()))
@@ -132,3 +137,67 @@ func TestRepository_RecalculateAccesses2(t *testing.T) {
assert.NoError(t, err)
assert.False(t, has)
}
func testRecalculateAccessesUpdateMode(t *testing.T) {
// Test the update path in refreshAccesses optimization
// Scenario: User's access mode changes from Read to Write
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.NoError(t, repo.LoadOwner(t.Context()))
// Verify initial access mode
_ = db.Insert(t.Context(), &repo_model.Collaboration{UserID: 4, RepoID: 4, Mode: perm_model.AccessModeWrite})
_ = db.Insert(t.Context(), &access_model.Access{UserID: 4, RepoID: 4, Mode: perm_model.AccessModeWrite})
initialAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err := db.GetEngine(t.Context()).Get(initialAccess)
assert.NoError(t, err)
assert.True(t, has)
initialMode := initialAccess.Mode
// Change collaboration mode to trigger update path
newMode := perm_model.AccessModeAdmin
assert.NotEqual(t, initialMode, newMode, "New mode should differ from initial mode")
_, err = db.GetEngine(t.Context()).
Where("user_id = ? AND repo_id = ?", 4, 4).
Cols("mode").
Update(&repo_model.Collaboration{Mode: newMode})
assert.NoError(t, err)
// Recalculate accesses - should UPDATE existing access, not delete+insert
assert.NoError(t, access_model.RecalculateAccesses(t.Context(), repo))
// Verify access was updated, not deleted and re-inserted
updatedAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err = db.GetEngine(t.Context()).Get(updatedAccess)
assert.NoError(t, err)
assert.True(t, has, "Access should still exist")
assert.Equal(t, newMode, updatedAccess.Mode, "Access mode should be updated to new collaboration mode")
}
func testRecalculateAccessesRemoveAccess(t *testing.T) {
// Test the delete path in refreshAccesses optimization
// Scenario: Remove a user's collaboration, access should be deleted
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.NoError(t, repo.LoadOwner(t.Context()))
// Verify initial access exists
initialAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err := db.GetEngine(t.Context()).Get(initialAccess)
assert.NoError(t, err)
assert.True(t, has, "Access should exist initially")
// Remove the collaboration to trigger delete path
_, err = db.GetEngine(t.Context()).
Where("user_id = ? AND repo_id = ?", 4, 4).
Delete(&repo_model.Collaboration{})
assert.NoError(t, err)
// Recalculate accesses - should DELETE the access record
assert.NoError(t, access_model.RecalculateAccesses(t.Context(), repo))
// Verify access was deleted
removedAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err = db.GetEngine(t.Context()).Get(removedAccess)
assert.NoError(t, err)
assert.False(t, has, "Access should be deleted after removing collaboration")
}
@@ -0,0 +1,155 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package access
import (
"testing"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
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 TestGetActionsUserRepoPermission(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
// Use fixtures for repos and users
repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) // Public, Owner 5, has Actions unit
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) // Private, Owner 2, no Actions unit in fixtures
repo15 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // Private, Owner 2, no Actions unit in fixtures
owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
actionsUser := user_model.NewActionsUser()
// Ensure repo2 and repo15 have Actions units for testing configuration
for _, r := range []*repo_model.Repository{repo2, repo15} {
require.NoError(t, db.Insert(ctx, &repo_model.RepoUnit{
RepoID: r.ID,
Type: unit.TypeActions,
Config: &repo_model.ActionsConfig{},
}))
}
t.Run("SameRepo_Public", func(t *testing.T) {
task47 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47})
require.Equal(t, repo4.ID, task47.RepoID)
perm, err := GetActionsUserRepoPermission(ctx, repo4, actionsUser, task47.ID)
require.NoError(t, err)
// Public repo, bot should have Read access even if not collaborator
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.True(t, perm.CanRead(unit.TypeCode))
})
t.Run("SameRepo_Private", func(t *testing.T) {
// Use Task 53 which is already in Repo 2 (Private)
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
require.Equal(t, repo2.ID, task53.RepoID)
perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID)
require.NoError(t, err)
// Private repo, bot has no base access, but gets Write from effective tokens perms (Permissive by default)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.True(t, perm.CanWrite(unit.TypeCode))
})
t.Run("CrossRepo_Denied_None", func(t *testing.T) {
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
// Set owner policy to nil allowed repos (None)
cfg := actions_model.OwnerActionsConfig{}
require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, cfg))
perm, err := GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID)
require.NoError(t, err)
// Should NOT have access to the private repo.
assert.False(t, perm.CanRead(unit.TypeCode))
})
t.Run("ForkPR_NoCrossRepo", func(t *testing.T) {
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
task53.IsForkPullRequest = true
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))
// Policy contains repo15
cfg := actions_model.OwnerActionsConfig{
AllowedCrossRepoIDs: []int64{repo15.ID},
}
require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, cfg))
perm, err := GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID)
require.NoError(t, err)
// Fork PR never gets cross-repo access to other private repos
assert.False(t, perm.CanRead(unit.TypeCode))
})
t.Run("Inheritance_And_Clamping", func(t *testing.T) {
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
task53.IsForkPullRequest = false
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))
// Owner policy: Restricted mode (Read-only Code)
ownerCfg := actions_model.OwnerActionsConfig{
TokenPermissionMode: repo_model.ActionsTokenPermissionModeRestricted,
MaxTokenPermissions: &repo_model.ActionsTokenPermissions{
UnitAccessModes: map[unit.Type]perm_model.AccessMode{
unit.TypeCode: perm_model.AccessModeRead,
},
},
}
require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, ownerCfg))
// Repo policy: OverrideOwnerConfig = false (should inherit owner's restricted mode)
repo2ActionsUnit := repo2.MustGetUnit(ctx, unit.TypeActions)
repo2ActionsCfg := repo2ActionsUnit.ActionsConfig()
repo2ActionsCfg.OverrideOwnerConfig = false
require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo2ActionsUnit))
perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID)
require.NoError(t, err)
// Should be clamped to Read-only
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeCode))
assert.False(t, perm.CanWrite(unit.TypeCode))
})
t.Run("RepoOverride_Clamping", func(t *testing.T) {
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
// Owner policy: Permissive (Write access)
ownerCfg := actions_model.OwnerActionsConfig{
TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive,
}
require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, ownerCfg))
// Repo policy: OverrideOwnerConfig = true, MaxTokenPermissions = Read
repo2ActionsUnit := repo2.MustGetUnit(ctx, unit.TypeActions)
repo2ActionsCfg := repo2ActionsUnit.ActionsConfig()
repo2ActionsCfg.OverrideOwnerConfig = true
repo2ActionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionModeRestricted
repo2ActionsCfg.MaxTokenPermissions = &repo_model.ActionsTokenPermissions{
UnitAccessModes: map[unit.Type]perm_model.AccessMode{
unit.TypeCode: perm_model.AccessModeRead,
},
}
require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo2ActionsUnit))
perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID)
require.NoError(t, err)
// Should be clamped to Read-only
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeCode))
})
}
@@ -5,9 +5,13 @@ package access
import (
"context"
"errors"
"fmt"
"maps"
"slices"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
perm_model "code.gitea.io/gitea/models/perm"
@@ -167,7 +171,8 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
}
func (p *Permission) LogString() string {
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): ["
var format strings.Builder
format.WriteString("<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [")
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
for i, u := range p.units {
@@ -179,19 +184,19 @@ func (p *Permission) LogString() string {
config = err.Error()
}
}
format += "\n\tunits[%d]: ID=%d RepoID=%d Type=%s Config=%s"
format.WriteString("\n\tunits[%d]: ID=%d RepoID=%d Type=%s Config=%s")
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
}
for key, value := range p.unitsMode {
format += "\n\tunitsMode[%-v]: %-v"
format.WriteString("\n\tunitsMode[%-v]: %-v")
args = append(args, key.LogString(), value.LogString())
}
format += "\n\tanonymousAccessMode: %-v"
format.WriteString("\n\tanonymousAccessMode: %-v")
args = append(args, p.anonymousAccessMode)
format += "\n\teveryoneAccessMode: %-v"
format.WriteString("\n\teveryoneAccessMode: %-v")
args = append(args, p.everyoneAccessMode)
format += "\n\t]>"
return fmt.Sprintf(format, args...)
format.WriteString("\n\t]>")
return fmt.Sprintf(format.String(), args...)
}
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
@@ -254,8 +259,139 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
}
}
// GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
func checkSameOwnerCrossRepoAccess(ctx context.Context, taskRepo, targetRepo *repo_model.Repository, isForkPR bool) bool {
if isForkPR {
// Fork PRs are never allowed cross-repo access to other private repositories of the owner.
return false
}
if taskRepo.OwnerID != targetRepo.OwnerID {
return false
}
ownerCfg, err := actions_model.GetOwnerActionsConfig(ctx, targetRepo.OwnerID)
if err != nil {
log.Error("GetOwnerActionsConfig: %v", err)
return false
}
return slices.Contains(ownerCfg.AllowedCrossRepoIDs, targetRepo.ID)
}
// GetActionsUserRepoPermission returns the actions user permissions to the repository
func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Repository, actionsUser *user_model.User, taskID int64) (perm Permission, err error) {
if actionsUser.ID != user_model.ActionsUserID {
return perm, errors.New("api GetActionsUserRepoPermission can only be called by the actions user")
}
task, err := actions_model.GetTaskByID(ctx, taskID)
if err != nil {
return perm, err
}
if err := task.LoadJob(ctx); err != nil {
return perm, err
}
var taskRepo *repo_model.Repository
if task.RepoID != repo.ID {
if err := task.Job.LoadRepo(ctx); err != nil {
return perm, err
}
taskRepo = task.Job.Repo
} else {
taskRepo = repo
}
// Compute effective permissions for this task against the target repo
effectivePerms, err := actions_model.ComputeTaskTokenPermissions(ctx, task, repo)
if err != nil {
return perm, err
}
if task.RepoID != repo.ID {
// Cross-repo access must also respect the target repo's permission ceiling.
targetRepoActionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
if targetRepoActionsCfg.OverrideOwnerConfig {
effectivePerms = targetRepoActionsCfg.ClampPermissions(effectivePerms)
} else {
targetRepoOwnerActionsCfg, err := actions_model.GetOwnerActionsConfig(ctx, repo.OwnerID)
if err != nil {
return perm, err
}
effectivePerms = targetRepoOwnerActionsCfg.ClampPermissions(effectivePerms)
}
}
if err := repo.LoadUnits(ctx); err != nil {
return perm, err
}
var maxPerm Permission
// Set up per-unit access modes based on configured permissions
maxPerm.units = repo.Units
maxPerm.unitsMode = maps.Clone(effectivePerms.UnitAccessModes)
// Check permission like simple user but limit to read-only (PR #36095)
// Enhanced to also grant read-only access if isSameRepo is true and target repository is public
botPerm, err := GetIndividualUserRepoPermission(ctx, repo, user_model.NewActionsUser())
if err != nil {
return perm, err
}
if botPerm.AccessMode >= perm_model.AccessModeRead {
// Public repo allows read access, increase permissions to at least read
// Otherwise you cannot access your own repository if your permissions are set to none but the repository is public
for _, u := range repo.Units {
if botPerm.CanRead(u.Type) {
maxPerm.unitsMode[u.Type] = max(maxPerm.unitsMode[u.Type], perm_model.AccessModeRead)
}
}
}
if task.RepoID == repo.ID {
return maxPerm, nil
}
if checkSameOwnerCrossRepoAccess(ctx, taskRepo, repo, task.IsForkPullRequest) {
// Access allowed by owner policy (grants access to private repos).
// Note: maxPerm has already been restricted to Read-Only in ComputeTaskTokenPermissions
// because isSameRepo is false.
return maxPerm, nil
}
// Fall through to allow public repository read access via botPerm check below
// Check if the repo is public or the Bot has explicit access
if botPerm.AccessMode >= perm_model.AccessModeRead {
return maxPerm, nil
}
// Check Collaborative Owner and explicit Bot permissions
// We allow access if:
// 1. It's a collaborative owner relationship
// 2. The Actions Bot user has been explicitly granted access and repository is private
// 3. The repository is public (handled by botPerm above)
if taskRepo.IsPrivate {
actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions)
if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) {
return maxPerm, nil
}
}
return perm, nil
}
// GetDoerRepoPermission returns the repository permission for the current actor,
// dispatching to GetActionsUserRepoPermission when the actor is an Actions token user.
func GetDoerRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) {
if taskID, ok := user_model.GetActionsUserTaskID(user); ok {
return GetActionsUserRepoPermission(ctx, repo, user, taskID)
}
return GetIndividualUserRepoPermission(ctx, repo, user)
}
// GetIndividualUserRepoPermission returns the permissions for an explicit user identity.
// In most request paths, callers should use GetDoerRepoPermission instead.
// Unlike GetDoerRepoPermission, this helper does not resolve Actions task users.
func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
defer func() {
if err == nil {
finalProcessRepoUnitPermission(user, &perm)
@@ -414,8 +550,9 @@ func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
// AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
// user does not have access.
// This helper only supports explicit user identities and does not resolve Actions task users.
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)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return perm_model.AccessModeNone, err
}
@@ -434,7 +571,7 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
if user.IsOrganization() {
return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
}
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
@@ -443,6 +580,7 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
}
// HasAnyUnitAccess see the comment of "perm.HasAnyUnitAccess"
// This helper only supports explicit user identities and does not resolve Actions task users.
func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
var user *user_model.User
var err error
@@ -452,7 +590,7 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
return false, err
}
}
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
@@ -500,13 +638,14 @@ func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository,
}
// CheckRepoUnitUser check whether user could visit the unit of this repository
// This helper only supports explicit user identities and does not resolve Actions task users.
func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
if user != nil && user.IsAdmin {
return true
}
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
log.Error("GetUserRepoPermission: %w", err)
log.Error("GetIndividualUserRepoPermission: %w", err)
return false
}
@@ -6,6 +6,7 @@ package access
import (
"testing"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
perm_model "code.gitea.io/gitea/models/perm"
@@ -157,8 +158,13 @@ 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())
func TestGetRepoPermission(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("GetIndividualUserRepoPermission", testGetIndividualUserRepoPermission)
t.Run("GetDoerRepoPermission", testGetDoerRepoPermission)
}
func testGetIndividualUserRepoPermission(t *testing.T) {
ctx := t.Context()
repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo
require.NoError(t, repo32.LoadOwner(ctx))
@@ -172,7 +178,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo32, user)
perm, err := GetIndividualUserRepoPermission(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
@@ -181,7 +187,7 @@ func TestGetUserRepoPermission(t *testing.T) {
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)
perm, err := GetIndividualUserRepoPermission(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])
@@ -191,7 +197,7 @@ func TestGetUserRepoPermission(t *testing.T) {
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)
perm, err := GetIndividualUserRepoPermission(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])
@@ -204,7 +210,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID}))
t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
@@ -214,7 +220,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead}))
t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
@@ -233,7 +239,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
@@ -245,3 +251,25 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, user.ID, users[0].ID)
})
}
func testGetDoerRepoPermission(t *testing.T) {
ctx := t.Context()
repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
task47 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47})
actionsDoer := user_model.NewActionsUserWithTaskID(task47.ID)
regularUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
actionsPerm, err := GetDoerRepoPermission(ctx, repo4, actionsDoer)
require.NoError(t, err)
directPerm, err := GetActionsUserRepoPermission(ctx, repo4, actionsDoer, task47.ID)
require.NoError(t, err)
assert.Equal(t, directPerm, actionsPerm)
doerPerm, err := GetDoerRepoPermission(ctx, repo1, regularUser)
require.NoError(t, err)
individualPerm, err := GetIndividualUserRepoPermission(ctx, repo1, regularUser)
require.NoError(t, err)
assert.Equal(t, individualPerm, doerPerm)
}