Skip to content

Commit d1bc442

Browse files
authored
Merge branch 'main' into webhook-bark
2 parents d6ee57d + 8085c75 commit d1bc442

File tree

23 files changed

+344
-228
lines changed

23 files changed

+344
-228
lines changed

custom/conf/app.example.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,6 +2541,12 @@ LEVEL = Info
25412541
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
25422542
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
25432543
;RENDER_CONTENT_MODE=sanitized
2544+
;;
2545+
;; Whether post-process the rendered HTML content, including:
2546+
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
2547+
;; mentioning users, rendering permlink code blocks, replacing emoji shorthands, etc.
2548+
;; By default, this is true when RENDER_CONTENT_MODE is `sanitized`, otherwise false.
2549+
;NEED_POST_PROCESS=false
25442550

25452551
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25462552
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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
}

modules/markup/external/external.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"code.gitea.io/gitea/modules/markup"
1616
"code.gitea.io/gitea/modules/process"
1717
"code.gitea.io/gitea/modules/setting"
18+
19+
"github.com/kballard/go-shellquote"
1820
)
1921

2022
// RegisterRenderers registers all supported third part renderers according settings
@@ -81,7 +83,10 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
8183
envMark("GITEA_PREFIX_SRC"), baseLinkSrc,
8284
envMark("GITEA_PREFIX_RAW"), baseLinkRaw,
8385
).Replace(p.Command)
84-
commands := strings.Fields(command)
86+
commands, err := shellquote.Split(command)
87+
if err != nil || len(commands) == 0 {
88+
return fmt.Errorf("%s invalid command %q: %w", p.Name(), p.Command, err)
89+
}
8590
args := commands[1:]
8691

8792
if p.IsInputFile {

modules/markup/render.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,31 +120,38 @@ func (ctx *RenderContext) WithHelper(helper RenderHelper) *RenderContext {
120120
return ctx
121121
}
122122

123-
// Render renders markup file to HTML with all specific handling stuff.
124-
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
123+
// FindRendererByContext finds renderer by RenderContext
124+
// TODO: it should be merged with other similar functions like GetRendererByFileName, DetectMarkupTypeByFileName, etc
125+
func FindRendererByContext(ctx *RenderContext) (Renderer, error) {
125126
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
126127
ctx.RenderOptions.MarkupType = DetectMarkupTypeByFileName(ctx.RenderOptions.RelativePath)
127128
if ctx.RenderOptions.MarkupType == "" {
128-
return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
129+
return nil, util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
129130
}
130131
}
131132

132133
renderer := renderers[ctx.RenderOptions.MarkupType]
133134
if renderer == nil {
134-
return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
135+
return nil, util.NewNotExistErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
135136
}
136137

137-
if ctx.RenderOptions.RelativePath != "" {
138-
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
139-
if !ctx.RenderOptions.InStandalonePage {
140-
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
141-
// otherwise, a <iframe> should be outputted to embed the external rendered page
142-
return renderIFrame(ctx, output)
143-
}
144-
}
138+
return renderer, nil
139+
}
140+
141+
func RendererNeedPostProcess(renderer Renderer) bool {
142+
if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() {
143+
return true
145144
}
145+
return false
146+
}
146147

147-
return render(ctx, renderer, input, output)
148+
// Render renders markup file to HTML with all specific handling stuff.
149+
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
150+
renderer, err := FindRendererByContext(ctx)
151+
if err != nil {
152+
return err
153+
}
154+
return RenderWithRenderer(ctx, renderer, input, output)
148155
}
149156

150157
// RenderString renders Markup string to HTML with all specific handling stuff and return string
@@ -185,7 +192,16 @@ func pipes() (io.ReadCloser, io.WriteCloser, func()) {
185192
}
186193
}
187194

188-
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
195+
func RenderWithRenderer(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
196+
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
197+
if !ctx.RenderOptions.InStandalonePage {
198+
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
199+
// otherwise, a <iframe> should be outputted to embed the external rendered page
200+
return renderIFrame(ctx, output)
201+
}
202+
// else: this is a standalone page, fallthrough to the real rendering
203+
}
204+
189205
ctx.usedByRender = true
190206
if ctx.RenderHelper != nil {
191207
defer ctx.RenderHelper.CleanUp()
@@ -214,7 +230,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
214230
}
215231

216232
eg.Go(func() (err error) {
217-
if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() {
233+
if RendererNeedPostProcess(renderer) {
218234
err = PostProcessDefault(ctx, pr1, pw2)
219235
} else {
220236
_, err = io.Copy(pw2, pr1)

modules/setting/markup.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ func newMarkupRenderer(name string, sec ConfigSection) {
259259
FileExtensions: exts,
260260
Command: command,
261261
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
262-
NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true),
263262
RenderContentMode: renderContentMode,
263+
264+
// if no sanitizer is needed, no post process is needed
265+
NeedPostProcess: sec.Key("NEED_POST_PROCESS").MustBool(renderContentMode == RenderContentModeSanitized),
264266
})
265267
}

0 commit comments

Comments
 (0)