Skip to content

Commit 1c0baee

Browse files
authored
Merge branch 'main' into fix-render-external
2 parents 8b7dfc0 + 08b9776 commit 1c0baee

File tree

15 files changed

+219
-169
lines changed

15 files changed

+219
-169
lines changed

models/git/lfs.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"fmt"
99

1010
"code.gitea.io/gitea/models/db"
11-
"code.gitea.io/gitea/models/perm"
1211
repo_model "code.gitea.io/gitea/models/repo"
1312
"code.gitea.io/gitea/models/unit"
1413
user_model "code.gitea.io/gitea/models/user"
@@ -42,30 +41,6 @@ func (err ErrLFSLockNotExist) Unwrap() error {
4241
return util.ErrNotExist
4342
}
4443

45-
// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
46-
type ErrLFSUnauthorizedAction struct {
47-
RepoID int64
48-
UserName string
49-
Mode perm.AccessMode
50-
}
51-
52-
// IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
53-
func IsErrLFSUnauthorizedAction(err error) bool {
54-
_, ok := err.(ErrLFSUnauthorizedAction)
55-
return ok
56-
}
57-
58-
func (err ErrLFSUnauthorizedAction) Error() string {
59-
if err.Mode == perm.AccessModeWrite {
60-
return fmt.Sprintf("User %s doesn't have write access for lfs lock [rid: %d]", err.UserName, err.RepoID)
61-
}
62-
return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
63-
}
64-
65-
func (err ErrLFSUnauthorizedAction) Unwrap() error {
66-
return util.ErrPermissionDenied
67-
}
68-
6944
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
7045
type ErrLFSLockAlreadyExist struct {
7146
RepoID int64
@@ -93,12 +68,6 @@ type ErrLFSFileLocked struct {
9368
UserName string
9469
}
9570

96-
// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
97-
func IsErrLFSFileLocked(err error) bool {
98-
_, ok := err.(ErrLFSFileLocked)
99-
return ok
100-
}
101-
10271
func (err ErrLFSFileLocked) Error() string {
10372
return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
10473
}

models/git/lfs_lock.go

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import (
1111
"time"
1212

1313
"code.gitea.io/gitea/models/db"
14-
"code.gitea.io/gitea/models/perm"
15-
access_model "code.gitea.io/gitea/models/perm/access"
1614
repo_model "code.gitea.io/gitea/models/repo"
17-
"code.gitea.io/gitea/models/unit"
1815
user_model "code.gitea.io/gitea/models/user"
1916
"code.gitea.io/gitea/modules/setting"
2017
"code.gitea.io/gitea/modules/util"
@@ -71,10 +68,6 @@ func (l *LFSLock) LoadOwner(ctx context.Context) error {
7168
// CreateLFSLock creates a new lock.
7269
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) {
7370
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
74-
if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil {
75-
return nil, err
76-
}
77-
7871
lock.Path = util.PathJoinRel(lock.Path)
7972
lock.RepoID = repo.ID
8073

@@ -165,10 +158,6 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
165158
return nil, err
166159
}
167160

168-
if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite); err != nil {
169-
return nil, err
170-
}
171-
172161
if !force && u.ID != lock.OwnerID {
173162
return nil, errors.New("user doesn't own lock and force flag is not set")
174163
}
@@ -180,22 +169,3 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
180169
return lock, nil
181170
})
182171
}
183-
184-
// CheckLFSAccessForRepo check needed access mode base on action
185-
func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.Repository, mode perm.AccessMode) error {
186-
if ownerID == 0 {
187-
return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode}
188-
}
189-
u, err := user_model.GetUserByID(ctx, ownerID)
190-
if err != nil {
191-
return err
192-
}
193-
perm, err := access_model.GetUserRepoPermission(ctx, repo, u)
194-
if err != nil {
195-
return err
196-
}
197-
if !perm.CanAccess(mode, unit.TypeCode) {
198-
return ErrLFSUnauthorizedAction{repo.ID, u.DisplayName(), mode}
199-
}
200-
return nil
201-
}

models/perm/access/repo_permission.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ package access
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"slices"
1011

12+
actions_model "code.gitea.io/gitea/models/actions"
1113
"code.gitea.io/gitea/models/db"
1214
"code.gitea.io/gitea/models/organization"
1315
perm_model "code.gitea.io/gitea/models/perm"
@@ -253,6 +255,34 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
253255
}
254256
}
255257

258+
// GetActionsUserRepoPermission returns the actions user permissions to the repository
259+
func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Repository, actionsUser *user_model.User, taskID int64) (perm Permission, err error) {
260+
if actionsUser.ID != user_model.ActionsUserID {
261+
return perm, errors.New("api GetActionsUserRepoPermission can only be called by the actions user")
262+
}
263+
task, err := actions_model.GetTaskByID(ctx, taskID)
264+
if err != nil {
265+
return perm, err
266+
}
267+
if task.RepoID != repo.ID {
268+
// FIXME allow public repo read access if tokenless pull is enabled
269+
return perm, nil
270+
}
271+
272+
var accessMode perm_model.AccessMode
273+
if task.IsForkPullRequest {
274+
accessMode = perm_model.AccessModeRead
275+
} else {
276+
accessMode = perm_model.AccessModeWrite
277+
}
278+
279+
if err := repo.LoadUnits(ctx); err != nil {
280+
return perm, err
281+
}
282+
perm.SetUnitsWithDefaultAccessMode(repo.Units, accessMode)
283+
return perm, nil
284+
}
285+
256286
// GetUserRepoPermission returns the user permissions to the repository
257287
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
258288
defer func() {

models/user/user.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,13 @@ func (u *User) MaxCreationLimit() int {
249249
}
250250

251251
// CanCreateRepoIn checks whether the doer(u) can create a repository in the owner
252-
// NOTE: functions calling this assume a failure due to repository count limit; it ONLY checks the repo number LIMIT, if new checks are added, those functions should be revised
252+
// NOTE: functions calling this assume a failure due to repository count limit, or the owner is not a real user.
253+
// It ONLY checks the repo number LIMIT or whether owner user is real. If new checks are added, those functions should be revised.
254+
// TODO: the callers can only return ErrReachLimitOfRepo, need to fine tune to support other error types in the future.
253255
func (u *User) CanCreateRepoIn(owner *User) bool {
256+
if u.ID <= 0 || owner.ID <= 0 {
257+
return false // fake user like Ghost or Actions user
258+
}
254259
if u.IsAdmin {
255260
return true
256261
}

models/user/user_system.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,16 @@ func IsGiteaActionsUserName(name string) bool {
4848
// NewActionsUser creates and returns a fake user for running the actions.
4949
func NewActionsUser() *User {
5050
return &User{
51-
ID: ActionsUserID,
52-
Name: ActionsUserName,
53-
LowerName: ActionsUserName,
54-
IsActive: true,
55-
FullName: "Gitea Actions",
56-
Email: ActionsUserEmail,
57-
KeepEmailPrivate: true,
58-
LoginName: ActionsUserName,
59-
Type: UserTypeBot,
60-
AllowCreateOrganization: true,
61-
Visibility: structs.VisibleTypePublic,
51+
ID: ActionsUserID,
52+
Name: ActionsUserName,
53+
LowerName: ActionsUserName,
54+
IsActive: true,
55+
FullName: "Gitea Actions",
56+
Email: ActionsUserEmail,
57+
KeepEmailPrivate: true,
58+
LoginName: ActionsUserName,
59+
Type: UserTypeBot,
60+
Visibility: structs.VisibleTypePublic,
6261
}
6362
}
6463

models/user/user_test.go

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -648,33 +648,36 @@ func TestGetInactiveUsers(t *testing.T) {
648648
func TestCanCreateRepo(t *testing.T) {
649649
defer test.MockVariableValue(&setting.Repository.MaxCreationLimit)()
650650
const noLimit = -1
651-
doerNormal := &user_model.User{}
652-
doerAdmin := &user_model.User{IsAdmin: true}
651+
doerActions := user_model.NewActionsUser()
652+
doerNormal := &user_model.User{ID: 2}
653+
doerAdmin := &user_model.User{ID: 1, IsAdmin: true}
653654
t.Run("NoGlobalLimit", func(t *testing.T) {
654655
setting.Repository.MaxCreationLimit = noLimit
655656

656-
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
657-
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
658-
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
657+
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
658+
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
659+
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
659660

660-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
661-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
662-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
661+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
662+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
663+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
664+
assert.False(t, doerActions.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
665+
assert.False(t, doerAdmin.CanCreateRepoIn(doerActions))
663666
})
664667

665668
t.Run("GlobalLimit50", func(t *testing.T) {
666669
setting.Repository.MaxCreationLimit = 50
667670

668-
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
669-
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit})) // limited by global limit
670-
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
671-
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
672-
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100}))
673-
674-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit}))
675-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit}))
676-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0}))
677-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100}))
678-
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100}))
671+
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
672+
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: noLimit})) // limited by global limit
673+
assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
674+
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
675+
assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: 100}))
676+
677+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: noLimit}))
678+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: noLimit}))
679+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 0}))
680+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 10, MaxRepoCreation: 100}))
681+
assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{ID: 2, NumRepos: 60, MaxRepoCreation: 100}))
679682
})
680683
}

routers/api/v1/api.go

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ import (
7070
"net/http"
7171
"strings"
7272

73-
actions_model "code.gitea.io/gitea/models/actions"
7473
auth_model "code.gitea.io/gitea/models/auth"
7574
"code.gitea.io/gitea/models/organization"
7675
"code.gitea.io/gitea/models/perm"
@@ -190,27 +189,11 @@ func repoAssignment() func(ctx *context.APIContext) {
190189

191190
if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
192191
taskID := ctx.Data["ActionsTaskID"].(int64)
193-
task, err := actions_model.GetTaskByID(ctx, taskID)
192+
ctx.Repo.Permission, err = access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
194193
if err != nil {
195194
ctx.APIErrorInternal(err)
196195
return
197196
}
198-
if task.RepoID != repo.ID {
199-
ctx.APIErrorNotFound()
200-
return
201-
}
202-
203-
if task.IsForkPullRequest {
204-
ctx.Repo.Permission.AccessMode = perm.AccessModeRead
205-
} else {
206-
ctx.Repo.Permission.AccessMode = perm.AccessModeWrite
207-
}
208-
209-
if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
210-
ctx.APIErrorInternal(err)
211-
return
212-
}
213-
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
214197
} else {
215198
needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
216199
if err != nil {

routers/api/v1/repo/repo.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
repo_module "code.gitea.io/gitea/modules/repository"
2929
"code.gitea.io/gitea/modules/setting"
3030
api "code.gitea.io/gitea/modules/structs"
31+
"code.gitea.io/gitea/modules/util"
3132
"code.gitea.io/gitea/modules/validation"
3233
"code.gitea.io/gitea/modules/web"
3334
"code.gitea.io/gitea/routers/api/v1/utils"
@@ -270,6 +271,8 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
270271
db.IsErrNamePatternNotAllowed(err) ||
271272
label.IsErrTemplateLoad(err) {
272273
ctx.APIError(http.StatusUnprocessableEntity, err)
274+
} else if errors.Is(err, util.ErrPermissionDenied) {
275+
ctx.APIError(http.StatusForbidden, err)
273276
} else {
274277
ctx.APIErrorInternal(err)
275278
}

routers/web/repo/githttp.go

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"sync"
1919
"time"
2020

21-
actions_model "code.gitea.io/gitea/models/actions"
2221
auth_model "code.gitea.io/gitea/models/auth"
2322
"code.gitea.io/gitea/models/perm"
2423
access_model "code.gitea.io/gitea/models/perm/access"
@@ -190,29 +189,17 @@ func httpBase(ctx *context.Context) *serviceHandler {
190189

191190
if ctx.Data["IsActionsToken"] == true {
192191
taskID := ctx.Data["ActionsTaskID"].(int64)
193-
task, err := actions_model.GetTaskByID(ctx, taskID)
192+
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
194193
if err != nil {
195-
ctx.ServerError("GetTaskByID", err)
196-
return nil
197-
}
198-
if task.RepoID != repo.ID {
199-
ctx.PlainText(http.StatusForbidden, "User permission denied")
194+
ctx.ServerError("GetUserRepoPermission", err)
200195
return nil
201196
}
202197

203-
if task.IsForkPullRequest {
204-
if accessMode > perm.AccessModeRead {
205-
ctx.PlainText(http.StatusForbidden, "User permission denied")
206-
return nil
207-
}
208-
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeRead))
209-
} else {
210-
if accessMode > perm.AccessModeWrite {
211-
ctx.PlainText(http.StatusForbidden, "User permission denied")
212-
return nil
213-
}
214-
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeWrite))
198+
if !p.CanAccess(accessMode, unitType) {
199+
ctx.PlainText(http.StatusNotFound, "Repository not found")
200+
return nil
215201
}
202+
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, p.UnitAccessMode(unitType)))
216203
} else {
217204
p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
218205
if err != nil {

services/convert/convert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
771771

772772
// ToLFSLock convert a LFSLock to api.LFSLock
773773
func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock {
774-
u, err := user_model.GetUserByID(ctx, l.OwnerID)
774+
u, err := user_model.GetPossibleUserByID(ctx, l.OwnerID)
775775
if err != nil {
776776
return nil
777777
}

0 commit comments

Comments
 (0)