Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions models/git/lfs_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ func (l *LFSLock) LoadOwner(ctx context.Context) error {
}

// CreateLFSLock creates a new lock.
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock) (*LFSLock, error) {
func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLock, taskID int64) (*LFSLock, error) {
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite); err != nil {
if err := CheckLFSAccessForRepo(ctx, lock.OwnerID, repo, perm.AccessModeWrite, taskID); err != nil {
return nil, err
}

Expand Down Expand Up @@ -158,14 +158,14 @@ func CountLFSLockByRepoID(ctx context.Context, repoID int64) (int64, error) {
}

// DeleteLFSLockByID deletes a lock by given ID.
func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repository, u *user_model.User, force bool) (*LFSLock, error) {
func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repository, u *user_model.User, force bool, taskID int64) (*LFSLock, error) {
return db.WithTx2(ctx, func(ctx context.Context) (*LFSLock, error) {
lock, err := GetLFSLockByID(ctx, id)
if err != nil {
return nil, err
}

if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite); err != nil {
if err := CheckLFSAccessForRepo(ctx, u.ID, repo, perm.AccessModeWrite, taskID); err != nil {
return nil, err
}

Expand All @@ -182,10 +182,23 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor
}

// CheckLFSAccessForRepo check needed access mode base on action
func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.Repository, mode perm.AccessMode) error {
func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.Repository, mode perm.AccessMode, taskID int64) error {
if ownerID == 0 {
return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode}
}
if ownerID == user_model.ActionsUserID {
if taskID == 0 {
return ErrLFSUnauthorizedAction{repo.ID, user_model.ActionsUserName, mode}
}
perm, err := access_model.GetActionsUserRepoPermission(ctx, repo, user_model.NewActionsUser(), taskID)
if err != nil {
return err
}
if !perm.CanAccess(mode, unit.TypeCode) {
return ErrLFSUnauthorizedAction{repo.ID, user_model.ActionsUserName, mode}
}
return nil
}
u, err := user_model.GetUserByID(ctx, ownerID)
if err != nil {
return err
Expand Down
30 changes: 30 additions & 0 deletions models/perm/access/repo_permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ package access

import (
"context"
"errors"
"fmt"
"slices"

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"
Expand Down Expand Up @@ -253,6 +255,34 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
}
}

// 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 task.RepoID != repo.ID {
// FIXME allow public repo read access if tokenless pull is enabled
return perm, nil
}

var accessMode perm_model.AccessMode
if task.IsForkPullRequest {
accessMode = perm_model.AccessModeRead
} else {
accessMode = perm_model.AccessModeWrite
}

if err := repo.LoadUnits(ctx); err != nil {
return perm, err
}
perm.SetUnitsWithDefaultAccessMode(repo.Units, accessMode)
return perm, nil
}

// 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) {
defer func() {
Expand Down
21 changes: 10 additions & 11 deletions models/user/user_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,16 @@ func IsGiteaActionsUserName(name string) bool {
// NewActionsUser creates and returns a fake user for running the actions.
func NewActionsUser() *User {
return &User{
ID: ActionsUserID,
Name: ActionsUserName,
LowerName: ActionsUserName,
IsActive: true,
FullName: "Gitea Actions",
Email: ActionsUserEmail,
KeepEmailPrivate: true,
LoginName: ActionsUserName,
Type: UserTypeBot,
AllowCreateOrganization: true,
Visibility: structs.VisibleTypePublic,
ID: ActionsUserID,
Name: ActionsUserName,
LowerName: ActionsUserName,
IsActive: true,
FullName: "Gitea Actions",
Email: ActionsUserEmail,
KeepEmailPrivate: true,
LoginName: ActionsUserName,
Type: UserTypeBot,
Visibility: structs.VisibleTypePublic,
}
}

Expand Down
19 changes: 1 addition & 18 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ import (
"net/http"
"strings"

actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
Expand Down Expand Up @@ -190,27 +189,11 @@ func repoAssignment() func(ctx *context.APIContext) {

if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
ctx.Repo.Permission, err = access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if task.RepoID != repo.ID {
ctx.APIErrorNotFound()
return
}

if task.IsForkPullRequest {
ctx.Repo.Permission.AccessMode = perm.AccessModeRead
} else {
ctx.Repo.Permission.AccessMode = perm.AccessModeWrite
}

if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
} else {
needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
if err != nil {
Expand Down
25 changes: 6 additions & 19 deletions routers/web/repo/githttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"sync"
"time"

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

if ctx.Data["IsActionsToken"] == true {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
if err != nil {
ctx.ServerError("GetTaskByID", err)
return nil
}
if task.RepoID != repo.ID {
ctx.PlainText(http.StatusForbidden, "User permission denied")
ctx.ServerError("GetUserRepoPermission", err)
return nil
}

if task.IsForkPullRequest {
if accessMode > perm.AccessModeRead {
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
}
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeRead))
} else {
if accessMode > perm.AccessModeWrite {
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
}
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, perm.AccessModeWrite))
if !p.CanAccess(accessMode, unitType) {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
environ = append(environ, fmt.Sprintf("%s=%d", repo_module.EnvActionPerm, p.UnitAccessMode(unitType)))
} else {
p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions routers/web/repo/setting/lfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func LFSLockFile(ctx *context.Context) {
_, err := git_model.CreateLFSLock(ctx, ctx.Repo.Repository, &git_model.LFSLock{
Path: lockPath,
OwnerID: ctx.Doer.ID,
})
}, 0)
if err != nil {
if git_model.IsErrLFSLockAlreadyExist(err) {
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_lock_already_exists", originalPath))
Expand All @@ -217,7 +217,7 @@ func LFSUnlock(ctx *context.Context) {
ctx.NotFound(nil)
return
}
_, err := git_model.DeleteLFSLockByID(ctx, ctx.PathParamInt64("lid"), ctx.Repo.Repository, ctx.Doer, true)
_, err := git_model.DeleteLFSLockByID(ctx, ctx.PathParamInt64("lid"), ctx.Repo.Repository, ctx.Doer, true, 0)
if err != nil {
ctx.ServerError("LFSUnlock", err)
return
Expand Down
8 changes: 7 additions & 1 deletion services/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,13 @@ func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {

// ToLFSLock convert a LFSLock to api.LFSLock
func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock {
u, err := user_model.GetUserByID(ctx, l.OwnerID)
var u *user_model.User
var err error
if l.OwnerID == user_model.ActionsUserID {
u = user_model.NewActionsUser()
} else {
u, err = user_model.GetUserByID(ctx, l.OwnerID)
}
if err != nil {
return nil
}
Expand Down
16 changes: 14 additions & 2 deletions services/lfs/locks.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,16 @@ func PostLockHandler(ctx *context.Context) {
return
}

var taskID int64
// Passing a non zero Actions Task ID as parameter if creating lock using Actions Job Token
if ctx.Data["IsActionsToken"] == true {
taskID = ctx.Data["ActionsTaskID"].(int64)
}

lock, err := git_model.CreateLFSLock(ctx, repository, &git_model.LFSLock{
Path: req.Path,
OwnerID: ctx.Doer.ID,
})
}, taskID)
if err != nil {
if git_model.IsErrLFSLockAlreadyExist(err) {
ctx.JSON(http.StatusConflict, api.LFSLockError{
Expand Down Expand Up @@ -315,7 +321,13 @@ func UnLockHandler(ctx *context.Context) {
return
}

lock, err := git_model.DeleteLFSLockByID(ctx, ctx.PathParamInt64("lid"), repository, ctx.Doer, req.Force)
var taskID int64
// Passing a non zero Actions Task ID as parameter if deleting lock using Actions Job Token
if ctx.Data["IsActionsToken"] == true {
taskID = ctx.Data["ActionsTaskID"].(int64)
}

lock, err := git_model.DeleteLFSLockByID(ctx, ctx.PathParamInt64("lid"), repository, ctx.Doer, req.Force, taskID)
if err != nil {
if git_model.IsErrLFSUnauthorizedAction(err) {
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="gitea-lfs"`)
Expand Down
14 changes: 3 additions & 11 deletions services/lfs/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"strings"
"time"

actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
git_model "code.gitea.io/gitea/models/git"
perm_model "code.gitea.io/gitea/models/perm"
Expand Down Expand Up @@ -549,19 +548,12 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho

if ctx.Data["IsActionsToken"] == true {
taskID := ctx.Data["ActionsTaskID"].(int64)
task, err := actions_model.GetTaskByID(ctx, taskID)
perm, err := access_model.GetActionsUserRepoPermission(ctx, repository, ctx.Doer, taskID)
if err != nil {
log.Error("Unable to GetTaskByID for task[%d] Error: %v", taskID, err)
log.Error("Unable to GetActionsUserRepoPermission for task[%d] Error: %v", taskID, err)
return false
}
if task.RepoID != repository.ID {
return false
}

if task.IsForkPullRequest {
return accessMode <= perm_model.AccessModeRead
}
return accessMode <= perm_model.AccessModeWrite
return perm.CanAccess(accessMode, unit.TypeCode)
}

// ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess
Expand Down
Loading