Compare commits
2 commits
7c22dab8a3
...
3b534855d8
Author | SHA1 | Date | |
---|---|---|---|
3b534855d8 | |||
136b8c7e0d |
73 changed files with 281 additions and 975 deletions
|
@ -13,42 +13,46 @@ groups:
|
||||||
-
|
-
|
||||||
name: BREAKING
|
name: BREAKING
|
||||||
labels:
|
labels:
|
||||||
- pr/breaking
|
- kind/breaking
|
||||||
-
|
-
|
||||||
name: SECURITY
|
name: SECURITY
|
||||||
labels:
|
labels:
|
||||||
- topic/security
|
- kind/security
|
||||||
-
|
-
|
||||||
name: FEATURES
|
name: FEATURES
|
||||||
labels:
|
labels:
|
||||||
- type/feature
|
- kind/feature
|
||||||
-
|
-
|
||||||
name: API
|
name: API
|
||||||
labels:
|
labels:
|
||||||
- modifies/api
|
- kind/api
|
||||||
-
|
-
|
||||||
name: ENHANCEMENTS
|
name: ENHANCEMENTS
|
||||||
labels:
|
labels:
|
||||||
- type/enhancement
|
- kind/enhancement
|
||||||
- type/refactoring
|
- kind/refactor
|
||||||
- topic/ui
|
- kind/ui
|
||||||
-
|
-
|
||||||
name: BUGFIXES
|
name: BUGFIXES
|
||||||
labels:
|
labels:
|
||||||
- type/bug
|
- kind/bug
|
||||||
-
|
-
|
||||||
name: TESTING
|
name: TESTING
|
||||||
labels:
|
labels:
|
||||||
- type/testing
|
- kind/testing
|
||||||
|
-
|
||||||
|
name: TRANSLATION
|
||||||
|
labels:
|
||||||
|
- kind/translation
|
||||||
-
|
-
|
||||||
name: BUILD
|
name: BUILD
|
||||||
labels:
|
labels:
|
||||||
- topic/build
|
- kind/build
|
||||||
- topic/code-linting
|
- kind/lint
|
||||||
-
|
-
|
||||||
name: DOCS
|
name: DOCS
|
||||||
labels:
|
labels:
|
||||||
- type/docs
|
- kind/docs
|
||||||
-
|
-
|
||||||
name: MISC
|
name: MISC
|
||||||
default: true
|
default: true
|
||||||
|
|
|
@ -10,8 +10,6 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
lint-backend:
|
lint-backend:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
|
||||||
image: 'docker.io/node:20-bookworm'
|
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
|
@ -24,8 +22,6 @@ jobs:
|
||||||
TAGS: bindata sqlite sqlite_unlock_notify
|
TAGS: bindata sqlite sqlite_unlock_notify
|
||||||
checks-backend:
|
checks-backend:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
|
||||||
image: 'docker.io/node:20-bookworm'
|
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
|
@ -38,7 +34,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [lint-backend, checks-backend]
|
needs: [lint-backend, checks-backend]
|
||||||
container:
|
container:
|
||||||
image: 'docker.io/node:20-bookworm'
|
image: codeberg.org/forgejo/test_env:1.20
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
|
@ -46,16 +42,15 @@ jobs:
|
||||||
go-version: "1.20"
|
go-version: "1.20"
|
||||||
- run: |
|
- run: |
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
chown -R gitea:gitea . /go
|
||||||
chown -R forgejo:forgejo .
|
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make deps-backend'
|
su gitea -c 'make deps-backend'
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make backend'
|
su gitea -c 'make backend'
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make unit-test-coverage test-check'
|
su gitea -c 'make unit-test-coverage test-check'
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
RACE_ENABLED: 'true'
|
RACE_ENABLED: 'true'
|
||||||
|
@ -64,7 +59,7 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [lint-backend, checks-backend]
|
needs: [lint-backend, checks-backend]
|
||||||
container:
|
container:
|
||||||
image: 'docker.io/node:20-bookworm'
|
image: codeberg.org/forgejo/test_env:1.20
|
||||||
services:
|
services:
|
||||||
mysql8:
|
mysql8:
|
||||||
image: mysql:8-debian
|
image: mysql:8-debian
|
||||||
|
@ -82,24 +77,17 @@ jobs:
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20"
|
go-version: "1.20"
|
||||||
- name: install dependencies
|
- run: |
|
||||||
run: |
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install --no-install-recommends -qq -y git-lfs
|
|
||||||
- name: setup user and permissions
|
|
||||||
run: |
|
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
chown -R gitea:gitea . /go
|
||||||
chown -R forgejo:forgejo .
|
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make deps-backend'
|
su gitea -c 'make deps-backend'
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make backend'
|
su gitea -c 'make backend'
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make test-mysql8-migration test-mysql8'
|
su gitea -c 'make test-mysql8-migration test-mysql8'
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
|
@ -108,10 +96,10 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [lint-backend, checks-backend]
|
needs: [lint-backend, checks-backend]
|
||||||
container:
|
container:
|
||||||
image: 'docker.io/node:20-bookworm'
|
image: codeberg.org/forgejo/test_env:1.20
|
||||||
services:
|
services:
|
||||||
pgsql:
|
pgsql:
|
||||||
image: 'docker.io/postgres:15'
|
image: postgres:15
|
||||||
env:
|
env:
|
||||||
POSTGRES_DB: test
|
POSTGRES_DB: test
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
@ -122,24 +110,17 @@ jobs:
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20"
|
go-version: "1.20"
|
||||||
- name: install dependencies
|
- run: |
|
||||||
run: |
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install --no-install-recommends -qq -y git-lfs
|
|
||||||
- name: setup user and permissions
|
|
||||||
run: |
|
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
chown -R gitea:gitea . /go
|
||||||
chown -R forgejo:forgejo .
|
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make deps-backend'
|
su gitea -c 'make deps-backend'
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make backend'
|
su gitea -c 'make backend'
|
||||||
env:
|
env:
|
||||||
TAGS: bindata
|
TAGS: bindata
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make test-pgsql-migration test-pgsql'
|
su gitea -c 'make test-pgsql-migration test-pgsql'
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit
|
TAGS: bindata gogit
|
||||||
|
@ -150,30 +131,23 @@ jobs:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
needs: [lint-backend, checks-backend]
|
needs: [lint-backend, checks-backend]
|
||||||
container:
|
container:
|
||||||
image: 'docker.io/node:20-bookworm'
|
image: codeberg.org/forgejo/test_env:1.20
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20"
|
go-version: "1.20"
|
||||||
- name: install dependencies
|
- run: |
|
||||||
run: |
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install --no-install-recommends -qq -y git-lfs
|
|
||||||
- name: setup user and permissions
|
|
||||||
run: |
|
|
||||||
git config --add safe.directory '*'
|
git config --add safe.directory '*'
|
||||||
adduser --quiet --comment forgejo --disabled-password forgejo
|
chown -R gitea:gitea . /go
|
||||||
chown -R forgejo:forgejo .
|
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make deps-backend'
|
su gitea -c 'make deps-backend'
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make backend'
|
su gitea -c 'make backend'
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||||
- run: |
|
- run: |
|
||||||
su forgejo -c 'make test-sqlite-migration test-sqlite'
|
su gitea -c 'make test-sqlite-migration test-sqlite'
|
||||||
timeout-minutes: 50
|
timeout-minutes: 50
|
||||||
env:
|
env:
|
||||||
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
TAGS: bindata gogit sqlite sqlite_unlock_notify
|
||||||
|
|
|
@ -3,7 +3,7 @@ pipeline:
|
||||||
image: docker.io/woodpeckerci/plugin-docker-buildx
|
image: docker.io/woodpeckerci/plugin-docker-buildx
|
||||||
settings:
|
settings:
|
||||||
repo: gitea.nulo.in/nulo/forgejo
|
repo: gitea.nulo.in/nulo/forgejo
|
||||||
tag: v1.20.5-1
|
tag: v1.20.5-0
|
||||||
registry: https://gitea.nulo.in
|
registry: https://gitea.nulo.in
|
||||||
username: Nulo
|
username: Nulo
|
||||||
password:
|
password:
|
||||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -4,33 +4,6 @@ This changelog goes through all the changes that have been made in each release
|
||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||||
|
|
||||||
## [1.20.5](https://github.com/go-gitea/gitea/releases/tag/1.20.5) - 2023-10-03
|
|
||||||
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Fix z-index on markdown completion (#27237) (#27242 & #27238)
|
|
||||||
* Use secure cookie for HTTPS sites (#26999) (#27013)
|
|
||||||
* BUGFIXES
|
|
||||||
* Fix git 2.11 error when checking IsEmpty (#27393) (#27396)
|
|
||||||
* Allow get release download files and lfs files with oauth2 token format (#26430) (#27378)
|
|
||||||
* Fix orphan check for deleted branch (#27310) (#27320)
|
|
||||||
* Quote table `release` in sql queries (#27205) (#27219)
|
|
||||||
* Fix release URL in webhooks (#27182) (#27184)
|
|
||||||
* Fix successful return value for `SyncAndGetUserSpecificDiff` (#27152) (#27156)
|
|
||||||
* fix pagination for followers and following (#27127) (#27138)
|
|
||||||
* Fix issue templates when blank isses are disabled (#27061) (#27082)
|
|
||||||
* Fix context cache bug & enable context cache for dashabord commits' authors(#26991) (#27017)
|
|
||||||
* Fix INI parsing for value with trailing slash (#26995) (#27001)
|
|
||||||
* Fix PushEvent NullPointerException jenkinsci/github-plugin (#27203) (#27249)
|
|
||||||
* Fix organization field being null in POST /orgs/{orgid}/teams (#27150) (#27167 & #27162)
|
|
||||||
* Fix bug of review request number (#27406) (#27104)
|
|
||||||
* TESTING
|
|
||||||
* services/wiki: Close() after error handling (#27129) (#27137)
|
|
||||||
* DOCS
|
|
||||||
* Improve actions docs related to `pull_request` event (#27126) (#27145)
|
|
||||||
* MISC
|
|
||||||
* Add logs for data broken of comment review (#27326) (#27344)
|
|
||||||
* Load reviewer before sending notification (#27063) (#27064)
|
|
||||||
|
|
||||||
## [1.20.4](https://github.com/go-gitea/gitea/releases/tag/v1.20.4) - 2023-09-08
|
## [1.20.4](https://github.com/go-gitea/gitea/releases/tag/v1.20.4) - 2023-09-08
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -89,7 +89,7 @@ endif
|
||||||
VERSION = ${GITEA_VERSION}
|
VERSION = ${GITEA_VERSION}
|
||||||
|
|
||||||
# SemVer
|
# SemVer
|
||||||
FORGEJO_VERSION := 5.0.6+0-gitea-1.20.5
|
FORGEJO_VERSION := 5.0.5+0-gitea-1.20.5
|
||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
|
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,6 @@ type ActionTaskOutput struct {
|
||||||
OutputValue string `xorm:"MEDIUMTEXT"`
|
OutputValue string `xorm:"MEDIUMTEXT"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
db.RegisterModel(new(ActionTaskOutput))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindTaskOutputByTaskID returns the outputs of the task.
|
// FindTaskOutputByTaskID returns the outputs of the task.
|
||||||
func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) {
|
func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) {
|
||||||
var outputs []*ActionTaskOutput
|
var outputs []*ActionTaskOutput
|
||||||
|
|
|
@ -232,7 +232,7 @@ func CreateSource(source *Source) error {
|
||||||
err = registerableSource.RegisterSource()
|
err = registerableSource.RegisterSource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// remove the AuthSource in case of errors while registering configuration
|
// remove the AuthSource in case of errors while registering configuration
|
||||||
if _, err := db.GetEngine(db.DefaultContext).ID(source.ID).Delete(new(Source)); err != nil {
|
if _, err := db.GetEngine(db.DefaultContext).Delete(source); err != nil {
|
||||||
log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
|
log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1014,7 +1014,6 @@ type FindCommentsOptions struct {
|
||||||
Type CommentType
|
Type CommentType
|
||||||
IssueIDs []int64
|
IssueIDs []int64
|
||||||
Invalidated util.OptionalBool
|
Invalidated util.OptionalBool
|
||||||
IsPull util.OptionalBool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToConds implements FindOptions interface
|
// ToConds implements FindOptions interface
|
||||||
|
@ -1049,9 +1048,6 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
|
||||||
if !opts.Invalidated.IsNone() {
|
if !opts.Invalidated.IsNone() {
|
||||||
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
|
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
|
||||||
}
|
}
|
||||||
if opts.IsPull != util.OptionalBoolNone {
|
|
||||||
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
|
|
||||||
}
|
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1059,7 +1055,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
|
||||||
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
|
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
|
||||||
comments := make([]*Comment, 0, 10)
|
comments := make([]*Comment, 0, 10)
|
||||||
sess := db.GetEngine(ctx).Where(opts.ToConds())
|
sess := db.GetEngine(ctx).Where(opts.ToConds())
|
||||||
if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone {
|
if opts.RepoID > 0 {
|
||||||
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -637,12 +637,12 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
|
||||||
userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType),
|
userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
cond = cond.Or(
|
||||||
// 4. Repositories that we directly own
|
// 4. Repositories that we directly own
|
||||||
cond = cond.Or(builder.Eq{"`repository`.owner_id": user.ID})
|
builder.Eq{"`repository`.owner_id": user.ID},
|
||||||
if !user.IsRestricted {
|
|
||||||
// 5. Be able to see all public repos in private organizations that we are an org_user of
|
// 5. Be able to see all public repos in private organizations that we are an org_user of
|
||||||
cond = cond.Or(userOrgPublicRepoCond(user.ID))
|
userOrgPublicRepoCond(user.ID),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cond
|
return cond
|
||||||
|
|
|
@ -7,7 +7,6 @@ package unittest
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -29,16 +28,6 @@ func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine) {
|
||||||
return db.DefaultContext.(*db.Context).Engine().(*xorm.Engine)
|
return db.DefaultContext.(*db.Context).Engine().(*xorm.Engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideFixtures(opts FixturesOptions, engine ...*xorm.Engine) func() {
|
|
||||||
old := fixturesLoader
|
|
||||||
if err := InitFixtures(opts, engine...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return func() {
|
|
||||||
fixturesLoader = old
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitFixtures initialize test fixtures for a test database
|
// InitFixtures initialize test fixtures for a test database
|
||||||
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
||||||
e := GetXORMEngine(engine...)
|
e := GetXORMEngine(engine...)
|
||||||
|
@ -48,12 +37,6 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
||||||
} else {
|
} else {
|
||||||
fixtureOptionFiles = testfixtures.Files(opts.Files...)
|
fixtureOptionFiles = testfixtures.Files(opts.Files...)
|
||||||
}
|
}
|
||||||
var fixtureOptionDirs []func(*testfixtures.Loader) error
|
|
||||||
if opts.Dirs != nil {
|
|
||||||
for _, dir := range opts.Dirs {
|
|
||||||
fixtureOptionDirs = append(fixtureOptionDirs, testfixtures.Directory(filepath.Join(opts.Base, dir)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialect := "unknown"
|
dialect := "unknown"
|
||||||
switch e.Dialect().URI().DBType {
|
switch e.Dialect().URI().DBType {
|
||||||
case schemas.POSTGRES:
|
case schemas.POSTGRES:
|
||||||
|
@ -74,7 +57,6 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
||||||
testfixtures.DangerousSkipTestDatabaseCheck(),
|
testfixtures.DangerousSkipTestDatabaseCheck(),
|
||||||
fixtureOptionFiles,
|
fixtureOptionFiles,
|
||||||
}
|
}
|
||||||
loaderOptions = append(loaderOptions, fixtureOptionDirs...)
|
|
||||||
|
|
||||||
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
||||||
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
|
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
|
||||||
|
|
|
@ -198,8 +198,6 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||||
type FixturesOptions struct {
|
type FixturesOptions struct {
|
||||||
Dir string
|
Dir string
|
||||||
Files []string
|
Files []string
|
||||||
Dirs []string
|
|
||||||
Base string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTestEngine creates a memory database and loads the fixture data from fixturesDir
|
// CreateTestEngine creates a memory database and loads the fixture data from fixturesDir
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/auth"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -196,6 +197,39 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOtpHeader(header http.Header) string {
|
||||||
|
otpHeader := header.Get("X-Gitea-OTP")
|
||||||
|
if forgejoHeader := header.Get("X-Forgejo-OTP"); forgejoHeader != "" {
|
||||||
|
otpHeader = forgejoHeader
|
||||||
|
}
|
||||||
|
return otpHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckForOTP validates OTP
|
||||||
|
func (ctx *APIContext) CheckForOTP() {
|
||||||
|
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
|
||||||
|
return // Skip 2FA
|
||||||
|
}
|
||||||
|
|
||||||
|
twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID)
|
||||||
|
if err != nil {
|
||||||
|
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||||
|
return // No 2FA enrollment for this user
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok, err := twofa.ValidateTOTP(getOtpHeader(ctx.Req.Header))
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.Error(http.StatusUnauthorized, "", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// APIContexter returns apicontext as middleware
|
// APIContexter returns apicontext as middleware
|
||||||
func APIContexter() func(http.Handler) http.Handler {
|
func APIContexter() func(http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
|
|
23
modules/context/api_forgejo_test.go
Normal file
23
modules/context/api_forgejo_test.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetOtpHeader(t *testing.T) {
|
||||||
|
header := http.Header{}
|
||||||
|
assert.EqualValues(t, "", getOtpHeader(header))
|
||||||
|
// Gitea
|
||||||
|
giteaOtp := "123456"
|
||||||
|
header.Set("X-Gitea-OTP", giteaOtp)
|
||||||
|
assert.EqualValues(t, giteaOtp, getOtpHeader(header))
|
||||||
|
// Forgejo has precedence
|
||||||
|
forgejoOtp := "abcdef"
|
||||||
|
header.Set("X-Forgejo-OTP", forgejoOtp)
|
||||||
|
assert.EqualValues(t, forgejoOtp, getOtpHeader(header))
|
||||||
|
}
|
|
@ -168,9 +168,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
||||||
// find protected branches without existing repository
|
// find protected branches without existing repository
|
||||||
genericOrphanCheck("Protected Branches without existing repository",
|
genericOrphanCheck("Protected Branches without existing repository",
|
||||||
"protected_branch", "repository", "protected_branch.repo_id=repository.id"),
|
"protected_branch", "repository", "protected_branch.repo_id=repository.id"),
|
||||||
// find deleted branches without existing repository
|
// find branches without existing repository
|
||||||
genericOrphanCheck("Deleted Branches without existing repository",
|
genericOrphanCheck("Branches without existing repository",
|
||||||
"deleted_branch", "repository", "deleted_branch.repo_id=repository.id"),
|
"branch", "repository", "branch.repo_id=repository.id"),
|
||||||
// find LFS locks without existing repository
|
// find LFS locks without existing repository
|
||||||
genericOrphanCheck("LFS locks without existing repository",
|
genericOrphanCheck("LFS locks without existing repository",
|
||||||
"lfs_lock", "repository", "lfs_lock.repo_id=repository.id"),
|
"lfs_lock", "repository", "lfs_lock.repo_id=repository.id"),
|
||||||
|
|
|
@ -7,17 +7,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDialContext returns a DialContext for Transport, the DialContext will do allow/block list check
|
// NewDialContext returns a DialContext for Transport, the DialContext will do allow/block list check
|
||||||
func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return NewDialContextWithProxy(usage, allowList, blockList, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDialContextWithProxy(usage string, allowList, blockList *HostMatchList, proxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
// How Go HTTP Client works with redirection:
|
// How Go HTTP Client works with redirection:
|
||||||
// transport.RoundTrip URL=http://domain.com, Host=domain.com
|
// transport.RoundTrip URL=http://domain.com, Host=domain.com
|
||||||
// transport.DialContext addrOrHost=domain.com:80
|
// transport.DialContext addrOrHost=domain.com:80
|
||||||
|
@ -31,18 +26,11 @@ func NewDialContextWithProxy(usage string, allowList, blockList *HostMatchList,
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
|
|
||||||
Control: func(network, ipAddr string, c syscall.RawConn) error {
|
Control: func(network, ipAddr string, c syscall.RawConn) (err error) {
|
||||||
host, port, err := net.SplitHostPort(addrOrHost)
|
var host string
|
||||||
if err != nil {
|
if host, _, err = net.SplitHostPort(addrOrHost); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if proxy != nil {
|
|
||||||
// Always allow the host of the proxy, but only on the specified port.
|
|
||||||
if host == proxy.Hostname() && port == proxy.Port() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// in Control func, the addr was already resolved to IP:PORT format, there is no cost to do ResolveTCPAddr here
|
// in Control func, the addr was already resolved to IP:PORT format, there is no cost to do ResolveTCPAddr here
|
||||||
tcpAddr, err := net.ResolveTCPAddr(network, ipAddr)
|
tcpAddr, err := net.ResolveTCPAddr(network, ipAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -66,7 +66,7 @@ var (
|
||||||
// well as the HTML5 spec:
|
// well as the HTML5 spec:
|
||||||
// http://spec.commonmark.org/0.28/#email-address
|
// http://spec.commonmark.org/0.28/#email-address
|
||||||
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
|
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
|
||||||
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
|
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|\\.(\\s|$))")
|
||||||
|
|
||||||
// blackfriday extensions create IDs like fn:user-content-footnote
|
// blackfriday extensions create IDs like fn:user-content-footnote
|
||||||
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)
|
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)
|
||||||
|
|
|
@ -264,18 +264,6 @@ func TestRender_email(t *testing.T) {
|
||||||
"send email to info@gitea.co.uk.",
|
"send email to info@gitea.co.uk.",
|
||||||
`<p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p>`)
|
`<p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p>`)
|
||||||
|
|
||||||
test(
|
|
||||||
`j.doe@example.com,
|
|
||||||
j.doe@example.com.
|
|
||||||
j.doe@example.com;
|
|
||||||
j.doe@example.com?
|
|
||||||
j.doe@example.com!`,
|
|
||||||
`<p><a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>,<br/>
|
|
||||||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>.<br/>
|
|
||||||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>;<br/>
|
|
||||||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?<br/>
|
|
||||||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`)
|
|
||||||
|
|
||||||
// Test that should *not* be turned into email links
|
// Test that should *not* be turned into email links
|
||||||
test(
|
test(
|
||||||
"\"info@gitea.com\"",
|
"\"info@gitea.com\"",
|
||||||
|
|
|
@ -16,7 +16,6 @@ type Package struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
|
||||||
|
|
||||||
s := fmt.Sprintf("<span class='ui label scope-parent' title='%s'>"+
|
s := fmt.Sprintf("<span class='ui label scope-parent' title='%s'>"+
|
||||||
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
||||||
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important''>%s</div>"+
|
||||||
"</span>",
|
"</span>",
|
||||||
description,
|
description,
|
||||||
textColor, scopeColor, scopeText,
|
textColor, scopeColor, scopeText,
|
||||||
|
|
|
@ -315,6 +315,10 @@ func reqToken() func(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.IsBasicAuth {
|
||||||
|
ctx.CheckForOTP()
|
||||||
|
return
|
||||||
|
}
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -336,6 +340,7 @@ func reqBasicAuth() func(ctx *context.APIContext) {
|
||||||
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
|
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.CheckForOTP()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,6 +687,12 @@ func bind[T any](_ T) any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
|
||||||
|
// in the session (if there is a user id stored in session other plugins might return the user
|
||||||
|
// object for that id).
|
||||||
|
//
|
||||||
|
// The Session plugin is expected to be executed second, in order to skip authentication
|
||||||
|
// for users that have already signed in.
|
||||||
func buildAuthGroup() *auth.Group {
|
func buildAuthGroup() *auth.Group {
|
||||||
group := auth.NewGroup(
|
group := auth.NewGroup(
|
||||||
&auth.OAuth2{},
|
&auth.OAuth2{},
|
||||||
|
@ -1154,8 +1165,8 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
m.Group("/{username}/{reponame}", func() {
|
m.Group("/{username}/{reponame}", func() {
|
||||||
m.Group("/issues", func() {
|
m.Group("/issues", func() {
|
||||||
m.Combo("").Get(repo.ListIssues).
|
m.Combo("").Get(repo.ListIssues).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue)
|
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
|
||||||
m.Get("/pinned", reqRepoReader(unit.TypeIssues), repo.ListPinnedIssues)
|
m.Get("/pinned", repo.ListPinnedIssues)
|
||||||
m.Group("/comments", func() {
|
m.Group("/comments", func() {
|
||||||
m.Get("", repo.ListRepoIssueComments)
|
m.Get("", repo.ListRepoIssueComments)
|
||||||
m.Group("/{id}", func() {
|
m.Group("/{id}", func() {
|
||||||
|
@ -1297,10 +1308,10 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
|
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
|
||||||
})
|
})
|
||||||
m.Group("/teams", func() {
|
m.Group("/teams", func() {
|
||||||
m.Get("", org.ListTeams)
|
m.Get("", reqToken(), org.ListTeams)
|
||||||
m.Post("", reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
|
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
|
||||||
m.Get("/search", org.SearchTeam)
|
m.Get("/search", reqToken(), org.SearchTeam)
|
||||||
}, reqToken(), reqOrgMembership())
|
}, reqOrgMembership())
|
||||||
m.Group("/labels", func() {
|
m.Group("/labels", func() {
|
||||||
m.Get("", org.ListLabels)
|
m.Get("", org.ListLabels)
|
||||||
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
|
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
|
||||||
|
|
|
@ -452,24 +452,6 @@ func ListIssues(ctx *context.APIContext) {
|
||||||
isPull = util.OptionalBoolNone
|
isPull = util.OptionalBoolNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPull != util.OptionalBoolNone && !ctx.Repo.CanWriteIssuesOrPulls(isPull.IsTrue()) {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPull == util.OptionalBoolNone {
|
|
||||||
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
|
|
||||||
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
|
|
||||||
if !canReadIssues && !canReadPulls {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
} else if !canReadIssues {
|
|
||||||
isPull = util.OptionalBoolTrue
|
|
||||||
} else if !canReadPulls {
|
|
||||||
isPull = util.OptionalBoolFalse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: we should be more efficient here
|
// FIXME: we should be more efficient here
|
||||||
createdByID := getUserIDForFilter(ctx, "created_by")
|
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
@ -580,10 +562,6 @@ func GetIssue(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue))
|
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,9 @@ import (
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
@ -71,11 +69,6 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
|
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
issue.Repo = ctx.Repo.Repository
|
issue.Repo = ctx.Repo.Repository
|
||||||
|
|
||||||
opts := &issues_model.FindCommentsOptions{
|
opts := &issues_model.FindCommentsOptions{
|
||||||
|
@ -272,27 +265,12 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var isPull util.OptionalBool
|
|
||||||
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
|
|
||||||
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
|
|
||||||
if canReadIssue && canReadPull {
|
|
||||||
isPull = util.OptionalBoolNone
|
|
||||||
} else if canReadIssue {
|
|
||||||
isPull = util.OptionalBoolFalse
|
|
||||||
} else if canReadPull {
|
|
||||||
isPull = util.OptionalBoolTrue
|
|
||||||
} else {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &issues_model.FindCommentsOptions{
|
opts := &issues_model.FindCommentsOptions{
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
Type: issues_model.CommentTypeComment,
|
Type: issues_model.CommentTypeComment,
|
||||||
Since: since,
|
Since: since,
|
||||||
Before: before,
|
Before: before,
|
||||||
IsPull: isPull,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
comments, err := issues_model.FindComments(ctx, opts)
|
comments, err := issues_model.FindComments(ctx, opts)
|
||||||
|
@ -379,11 +357,6 @@ func CreateIssueComment(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
|
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
|
||||||
ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
|
ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
|
||||||
return
|
return
|
||||||
|
@ -457,11 +430,6 @@ func GetIssueComment(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Type != issues_model.CommentTypeComment {
|
if comment.Type != issues_model.CommentTypeComment {
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
|
@ -580,17 +548,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
|
||||||
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
|
||||||
ctx.Status(http.StatusForbidden)
|
ctx.Status(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -693,17 +651,7 @@ func deleteIssueComment(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
|
||||||
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
|
||||||
ctx.Status(http.StatusForbidden)
|
ctx.Status(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
} else if comment.Type != issues_model.CommentTypeComment {
|
} else if comment.Type != issues_model.CommentTypeComment {
|
||||||
|
|
|
@ -61,12 +61,6 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
if err := comment.LoadIssue(ctx); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
|
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||||
|
@ -192,19 +186,9 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = comment.LoadIssue(ctx); err != nil {
|
err = comment.LoadIssue(ctx)
|
||||||
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
|
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
|
||||||
ctx.NotFound()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
|
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
|
||||||
|
|
|
@ -155,11 +155,6 @@ func GetDeployKey(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = key.GetContent(); err != nil {
|
if err = key.GetContent(); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetContent", err)
|
ctx.Error(http.StatusInternalServerError, "GetContent", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -19,19 +19,19 @@ import (
|
||||||
"code.gitea.io/gitea/modules/web/routing"
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc {
|
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
|
||||||
prefix = strings.Trim(prefix, "/")
|
prefix = strings.Trim(prefix, "/")
|
||||||
funcInfo := routing.GetFuncInfo(storageHandler, prefix)
|
funcInfo := routing.GetFuncInfo(storageHandler, prefix)
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
if storageSetting.MinioConfig.ServeDirect {
|
if storageSetting.MinioConfig.ServeDirect {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method != "GET" && req.Method != "HEAD" {
|
if req.Method != "GET" && req.Method != "HEAD" {
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
next.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
next.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||||
|
@ -43,7 +43,7 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||||
log.Warn("Unable to find %s %s", prefix, rPath)
|
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
http.Error(w, "file not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
|
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
|
||||||
|
@ -57,12 +57,12 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method != "GET" && req.Method != "HEAD" {
|
if req.Method != "GET" && req.Method != "HEAD" {
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
next.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
next.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||||
|
@ -70,7 +70,7 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
|
||||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||||
rPath = util.PathJoinRelX(rPath)
|
rPath = util.PathJoinRelX(rPath)
|
||||||
if rPath == "" || rPath == "." {
|
if rPath == "" || rPath == "." {
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
http.Error(w, "file not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||||
log.Warn("Unable to find %s %s", prefix, rPath)
|
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
http.Error(w, "file not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||||
|
@ -95,4 +95,5 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
|
||||||
defer fr.Close()
|
defer fr.Close()
|
||||||
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
|
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
|
||||||
"code.gitea.io/gitea/routers/web/repo"
|
|
||||||
context_service "code.gitea.io/gitea/services/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func requireSignIn(ctx *context.Context) {
|
|
||||||
if !setting.Service.RequireSignInView {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// rely on the results of Contexter
|
|
||||||
if !ctx.IsSigned {
|
|
||||||
// TODO: support digit auth - which would be Authorization header with digit
|
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
|
||||||
ctx.Error(http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gitHTTPRouters(m *web.Route) {
|
|
||||||
m.Group("", func() {
|
|
||||||
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
|
|
||||||
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
|
|
||||||
m.GetOptions("/info/refs", repo.GetInfoRefs)
|
|
||||||
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
|
|
||||||
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
|
|
||||||
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
|
|
||||||
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
|
|
||||||
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
|
|
||||||
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
|
|
||||||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
|
|
||||||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
|
|
||||||
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
|
|
||||||
}
|
|
|
@ -251,6 +251,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
|
||||||
isSameRepo = true
|
isSameRepo = true
|
||||||
ci.HeadUser = ctx.Repo.Owner
|
ci.HeadUser = ctx.Repo.Owner
|
||||||
ci.HeadBranch = headInfos[0]
|
ci.HeadBranch = headInfos[0]
|
||||||
|
|
||||||
} else if len(headInfos) == 2 {
|
} else if len(headInfos) == 2 {
|
||||||
headInfosSplit := strings.Split(headInfos[0], "/")
|
headInfosSplit := strings.Split(headInfos[0], "/")
|
||||||
if len(headInfosSplit) == 1 {
|
if len(headInfosSplit) == 1 {
|
||||||
|
@ -405,9 +406,6 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer ci.HeadGitRepo.Close()
|
defer ci.HeadGitRepo.Close()
|
||||||
} else {
|
|
||||||
ctx.NotFound("ParseCompareInfo", nil)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["HeadRepo"] = ci.HeadRepo
|
ctx.Data["HeadRepo"] = ci.HeadRepo
|
||||||
|
|
|
@ -2971,11 +2971,6 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
ctx.Error(http.StatusForbidden)
|
ctx.Error(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
|
@ -3042,11 +3037,6 @@ func DeleteComment(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
ctx.Error(http.StatusForbidden)
|
ctx.Error(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
|
@ -3173,11 +3163,6 @@ func ChangeCommentReaction(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull)) {
|
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull)) {
|
||||||
if log.IsTrace() {
|
if log.IsTrace() {
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
|
@ -3321,16 +3306,6 @@ func GetCommentAttachments(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comment.LoadIssue(ctx); err != nil {
|
|
||||||
ctx.NotFoundOrServerError("LoadIssue", issues_model.IsErrIssueNotExist, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !comment.Type.HasAttachmentSupport() {
|
if !comment.Type.HasAttachmentSupport() {
|
||||||
ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
|
ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
|
||||||
return
|
return
|
||||||
|
|
|
@ -125,10 +125,6 @@ func GetContentHistoryDetail(ctx *context.Context) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if history.IssueID != issue.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the related comment if this history revision is for a comment, otherwise the history revision is for an issue.
|
// get the related comment if this history revision is for a comment, otherwise the history revision is for an issue.
|
||||||
var comment *issues_model.Comment
|
var comment *issues_model.Comment
|
||||||
|
@ -198,19 +194,11 @@ func SoftDeleteContentHistory(ctx *context.Context) {
|
||||||
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
|
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if comment.IssueID != issue.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
|
if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
|
||||||
log.Error("can not get issue content history %v. err=%v", historyID, err)
|
log.Error("can not get issue content history %v. err=%v", historyID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if history.IssueID != issue.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history)
|
canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history)
|
||||||
if !canSoftDelete {
|
if !canSoftDelete {
|
||||||
|
|
|
@ -89,10 +89,6 @@ func IssuePinMove(ctx *context.Context) {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if issue.RepoID != ctx.Repo.Repository.ID {
|
|
||||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = issue.MovePin(ctx, form.Position)
|
err = issue.MovePin(ctx, form.Position)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -592,17 +592,7 @@ func DeleteTag(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
||||||
id := ctx.FormInt64("id")
|
if err := releaseservice.DeleteReleaseByID(ctx, ctx.FormInt64("id"), ctx.Doer, isDelTag); err != nil {
|
||||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetRelease", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ctx.Repo.Repository.ID != rel.RepoID {
|
|
||||||
ctx.NotFound("CompareRepoID", repo_model.ErrReleaseNotExist{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := releaseservice.DeleteReleaseByID(ctx, id, ctx.Doer, isDelTag); err != nil {
|
|
||||||
if models.IsErrProtectedTagName(err) {
|
if models.IsErrProtectedTagName(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -821,11 +821,6 @@ func UsernameSubRoute(ctx *context.Context) {
|
||||||
reloadParam := func(suffix string) (success bool) {
|
reloadParam := func(suffix string) (success bool) {
|
||||||
ctx.SetParams("username", strings.TrimSuffix(username, suffix))
|
ctx.SetParams("username", strings.TrimSuffix(username, suffix))
|
||||||
context_service.UserAssignmentWeb()(ctx)
|
context_service.UserAssignmentWeb()(ctx)
|
||||||
// check view permissions
|
|
||||||
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
|
||||||
ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !ctx.Written()
|
return !ctx.Written()
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -422,7 +422,7 @@ func PackageSettingsPost(ctx *context.Context) {
|
||||||
|
|
||||||
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
|
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
|
||||||
// redirect to the package if there are still versions available
|
// redirect to the package if there are still versions available
|
||||||
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: util.OptionalBoolFalse}); has {
|
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID}); has {
|
||||||
redirectURL = ctx.Package.Descriptor.PackageWebLink()
|
redirectURL = ctx.Package.Descriptor.PackageWebLink()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,8 +175,6 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignSignInAndCsrf = auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{DisableCSRF: true})
|
|
||||||
|
|
||||||
// registerRoutes register routes
|
// registerRoutes register routes
|
||||||
func registerRoutes(m *web.Route) {
|
func registerRoutes(m *web.Route) {
|
||||||
reqSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true})
|
reqSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true})
|
||||||
|
@ -184,6 +182,7 @@ func registerRoutes(m *web.Route) {
|
||||||
// TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView)
|
// TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView)
|
||||||
ignSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
|
ignSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
|
||||||
ignExploreSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
|
ignExploreSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
|
||||||
|
ignSignInAndCsrf := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{DisableCSRF: true})
|
||||||
validation.AddBindingRules()
|
validation.AddBindingRules()
|
||||||
|
|
||||||
linkAccountEnabled := func(ctx *context.Context) {
|
linkAccountEnabled := func(ctx *context.Context) {
|
||||||
|
@ -1392,7 +1391,19 @@ func registerRoutes(m *web.Route) {
|
||||||
})
|
})
|
||||||
}, ignSignInAndCsrf, lfsServerEnabled)
|
}, ignSignInAndCsrf, lfsServerEnabled)
|
||||||
|
|
||||||
gitHTTPRouters(m)
|
m.Group("", func() {
|
||||||
|
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
|
||||||
|
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
|
||||||
|
m.GetOptions("/info/refs", repo.GetInfoRefs)
|
||||||
|
m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
|
||||||
|
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
|
||||||
|
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
|
||||||
|
m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
|
||||||
|
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
|
||||||
|
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
|
||||||
|
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
|
||||||
|
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
|
||||||
|
}, ignSignInAndCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// ***** END: Repository *****
|
// ***** END: Repository *****
|
||||||
|
|
|
@ -37,16 +37,12 @@ func isContainerPath(req *http.Request) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gitRawOrAttachPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`)
|
gitRawReleasePathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/))`)
|
||||||
lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
|
lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func isGitRawOrAttachPath(req *http.Request) bool {
|
func isGitRawReleaseOrLFSPath(req *http.Request) bool {
|
||||||
return gitRawOrAttachPathRe.MatchString(req.URL.Path)
|
if gitRawReleasePathRe.MatchString(req.URL.Path) {
|
||||||
}
|
|
||||||
|
|
||||||
func isGitRawOrAttachOrLFSPath(req *http.Request) bool {
|
|
||||||
if isGitRawOrAttachPath(req) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if setting.LFS.StartServer {
|
if setting.LFS.StartServer {
|
||||||
|
|
|
@ -85,10 +85,6 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
||||||
"/owner/repo/releases/download/tag/repo.tar.gz",
|
"/owner/repo/releases/download/tag/repo.tar.gz",
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"/owner/repo/attachments/6d92a9ee-5d8b-4993-97c9-6181bdaa8955",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
lfsTests := []string{
|
lfsTests := []string{
|
||||||
"/owner/repo/info/lfs/",
|
"/owner/repo/info/lfs/",
|
||||||
|
@ -108,11 +104,11 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
||||||
t.Run(tt.path, func(t *testing.T) {
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
|
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
|
||||||
setting.LFS.StartServer = false
|
setting.LFS.StartServer = false
|
||||||
if got := isGitRawOrAttachOrLFSPath(req); got != tt.want {
|
if got := isGitRawReleaseOrLFSPath(req); got != tt.want {
|
||||||
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
setting.LFS.StartServer = true
|
setting.LFS.StartServer = true
|
||||||
if got := isGitRawOrAttachOrLFSPath(req); got != tt.want {
|
if got := isGitRawReleaseOrLFSPath(req); got != tt.want {
|
||||||
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -121,11 +117,11 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
||||||
t.Run(tt, func(t *testing.T) {
|
t.Run(tt, func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("POST", tt, nil)
|
req, _ := http.NewRequest("POST", tt, nil)
|
||||||
setting.LFS.StartServer = false
|
setting.LFS.StartServer = false
|
||||||
if got := isGitRawOrAttachOrLFSPath(req); got != setting.LFS.StartServer {
|
if got := isGitRawReleaseOrLFSPath(req); got != setting.LFS.StartServer {
|
||||||
t.Errorf("isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawOrAttachPathRe.MatchString(tt))
|
t.Errorf("isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawReleasePathRe.MatchString(tt))
|
||||||
}
|
}
|
||||||
setting.LFS.StartServer = true
|
setting.LFS.StartServer = true
|
||||||
if got := isGitRawOrAttachOrLFSPath(req); got != setting.LFS.StartServer {
|
if got := isGitRawReleaseOrLFSPath(req); got != setting.LFS.StartServer {
|
||||||
t.Errorf("isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
|
t.Errorf("isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ func (b *Basic) Name() string {
|
||||||
// Returns nil if header is empty or validation fails.
|
// Returns nil if header is empty or validation fails.
|
||||||
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||||
// Basic authentication should only fire on API, Download or on Git or LFSPaths
|
// Basic authentication should only fire on API, Download or on Git or LFSPaths
|
||||||
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,38 +132,11 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
if skipper, ok := source.Cfg.(LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() {
|
||||||
if err := validateTOTP(req, u); err != nil {
|
store.GetData()["SkipLocalTwoFA"] = true
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("Basic Authorization: Logged in user %-v", u)
|
log.Trace("Basic Authorization: Logged in user %-v", u)
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOtpHeader(header http.Header) string {
|
|
||||||
otpHeader := header.Get("X-Gitea-OTP")
|
|
||||||
if forgejoHeader := header.Get("X-Forgejo-OTP"); forgejoHeader != "" {
|
|
||||||
otpHeader = forgejoHeader
|
|
||||||
}
|
|
||||||
return otpHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateTOTP(req *http.Request, u *user_model.User) error {
|
|
||||||
twofa, err := auth_model.GetTwoFactorByUID(u.ID)
|
|
||||||
if err != nil {
|
|
||||||
if auth_model.IsErrTwoFactorNotEnrolled(err) {
|
|
||||||
// No 2FA enrollment for this user
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ok, err := twofa.ValidateTOTP(getOtpHeader(req.Header)); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !ok {
|
|
||||||
return util.NewInvalidArgumentErrorf("invalid provided OTP")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
@ -215,6 +216,31 @@ func VerifyAuthWithOptionsAPI(options *VerifyOptions) func(ctx *context.APIConte
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if ctx.IsSigned && ctx.IsBasicAuth {
|
||||||
|
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
|
||||||
|
return // Skip 2FA
|
||||||
|
}
|
||||||
|
twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID)
|
||||||
|
if err != nil {
|
||||||
|
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||||
|
return // No 2FA enrollment for this user
|
||||||
|
}
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
|
||||||
|
ok, err := twofa.ValidateTOTP(otpHeader)
|
||||||
|
if err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.JSON(http.StatusForbidden, map[string]string{
|
||||||
|
"message": "Only signed in user is allowed to call APIs.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.AdminRequired {
|
if options.AdminRequired {
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (o *OAuth2) userIDFromToken(tokenSHA string, store DataStore) int64 {
|
||||||
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||||
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
|
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
|
||||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
|
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
|
||||||
!isGitRawOrAttachPath(req) {
|
!gitRawReleasePathRe.MatchString(req.URL.Path) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store Da
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure requests to API paths, attachment downloads, git and LFS do not create a new session
|
// Make sure requests to API paths, attachment downloads, git and LFS do not create a new session
|
||||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) {
|
||||||
if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
|
if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
|
||||||
handleSignIn(w, req, sess, user)
|
handleSignIn(w, req, sess, user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
package convert
|
package convert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +16,12 @@ func WebAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachm
|
||||||
}
|
}
|
||||||
|
|
||||||
func APIAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
|
func APIAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
|
||||||
return attach.DownloadURL()
|
if attach.CustomDownloadURL != "" {
|
||||||
|
return attach.CustomDownloadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id}
|
||||||
|
return setting.AppURL + "api/repos/" + repo.FullName() + "/releases/" + strconv.FormatInt(attach.ReleaseID, 10) + "/assets/" + strconv.FormatInt(attach.ID, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToAttachment converts models.Attachment to api.Attachment for API usage
|
// ToAttachment converts models.Attachment to api.Attachment for API usage
|
||||||
|
|
|
@ -35,7 +35,6 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m
|
||||||
Name: pd.Package.Name,
|
Name: pd.Package.Name,
|
||||||
Version: pd.Version.Version,
|
Version: pd.Version.Version,
|
||||||
CreatedAt: pd.Version.CreatedUnix.AsTime(),
|
CreatedAt: pd.Version.CreatedUnix.AsTime(),
|
||||||
HTMLURL: pd.FullWebLink(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,10 +58,6 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
|
||||||
oldTitle := issue.Title
|
oldTitle := issue.Title
|
||||||
issue.Title = title
|
issue.Title = title
|
||||||
|
|
||||||
if oldTitle == title {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
|
if err = issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,8 +282,6 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
|
||||||
httpClient := NewMigrationHTTPClient()
|
httpClient := NewMigrationHTTPClient()
|
||||||
|
|
||||||
for _, asset := range rel.Attachments {
|
for _, asset := range rel.Attachments {
|
||||||
assetID := asset.ID // Don't optimize this, for closure we need a local variable
|
|
||||||
assetDownloadURL := asset.DownloadURL
|
|
||||||
size := int(asset.Size)
|
size := int(asset.Size)
|
||||||
dlCount := int(asset.DownloadCount)
|
dlCount := int(asset.DownloadCount)
|
||||||
r.Assets = append(r.Assets, &base.ReleaseAsset{
|
r.Assets = append(r.Assets, &base.ReleaseAsset{
|
||||||
|
@ -294,18 +292,18 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
|
||||||
Created: asset.Created,
|
Created: asset.Created,
|
||||||
DownloadURL: &asset.DownloadURL,
|
DownloadURL: &asset.DownloadURL,
|
||||||
DownloadFunc: func() (io.ReadCloser, error) {
|
DownloadFunc: func() (io.ReadCloser, error) {
|
||||||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, assetID)
|
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, asset.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasBaseURL(assetDownloadURL, g.baseURL) {
|
if !hasBaseURL(asset.DownloadURL, g.baseURL) {
|
||||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, assetDownloadURL)
|
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, asset.DownloadURL)
|
||||||
return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil
|
return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: for a private download?
|
// FIXME: for a private download?
|
||||||
req, err := http.NewRequest("GET", assetDownloadURL, nil)
|
req, err := http.NewRequest("GET", asset.DownloadURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,7 +309,6 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
|
||||||
httpClient := NewMigrationHTTPClient()
|
httpClient := NewMigrationHTTPClient()
|
||||||
|
|
||||||
for k, asset := range rel.Assets.Links {
|
for k, asset := range rel.Assets.Links {
|
||||||
assetID := asset.ID // Don't optimize this, for closure we need a local variable
|
|
||||||
r.Assets = append(r.Assets, &base.ReleaseAsset{
|
r.Assets = append(r.Assets, &base.ReleaseAsset{
|
||||||
ID: int64(asset.ID),
|
ID: int64(asset.ID),
|
||||||
Name: asset.Name,
|
Name: asset.Name,
|
||||||
|
@ -317,13 +316,13 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
|
||||||
Size: &zero,
|
Size: &zero,
|
||||||
DownloadCount: &zero,
|
DownloadCount: &zero,
|
||||||
DownloadFunc: func() (io.ReadCloser, error) {
|
DownloadFunc: func() (io.ReadCloser, error) {
|
||||||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(g.ctx))
|
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, asset.ID, gitlab.WithContext(g.ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasBaseURL(link.URL, g.baseURL) {
|
if !hasBaseURL(link.URL, g.baseURL) {
|
||||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, link.URL)
|
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, link.URL)
|
||||||
return io.NopCloser(strings.NewReader(link.URL)), nil
|
return io.NopCloser(strings.NewReader(link.URL)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,6 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
|
||||||
if err := pr.LoadIssue(ctx); err != nil {
|
if err := pr.LoadIssue(ctx); err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
if err := pr.Issue.LoadPoster(ctx); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker)
|
isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker)
|
||||||
issueReference := "#"
|
issueReference := "#"
|
||||||
|
|
|
@ -243,7 +243,7 @@ var (
|
||||||
hostMatchers []glob.Glob
|
hostMatchers []glob.Glob
|
||||||
)
|
)
|
||||||
|
|
||||||
func webhookProxy(allowList *hostmatcher.HostMatchList) func(req *http.Request) (*url.URL, error) {
|
func webhookProxy() func(req *http.Request) (*url.URL, error) {
|
||||||
if setting.Webhook.ProxyURL == "" {
|
if setting.Webhook.ProxyURL == "" {
|
||||||
return proxy.Proxy()
|
return proxy.Proxy()
|
||||||
}
|
}
|
||||||
|
@ -261,9 +261,6 @@ func webhookProxy(allowList *hostmatcher.HostMatchList) func(req *http.Request)
|
||||||
return func(req *http.Request) (*url.URL, error) {
|
return func(req *http.Request) (*url.URL, error) {
|
||||||
for _, v := range hostMatchers {
|
for _, v := range hostMatchers {
|
||||||
if v.Match(req.URL.Host) {
|
if v.Match(req.URL.Host) {
|
||||||
if !allowList.MatchHostName(req.URL.Host) {
|
|
||||||
return nil, fmt.Errorf("webhook can only call allowed HTTP servers (check your %s setting), deny '%s'", allowList.SettingKeyHint, req.URL.Host)
|
|
||||||
}
|
|
||||||
return http.ProxyURL(setting.Webhook.ProxyURLFixed)(req)
|
return http.ProxyURL(setting.Webhook.ProxyURLFixed)(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,8 +282,8 @@ func Init() error {
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
|
||||||
Proxy: webhookProxy(allowedHostMatcher),
|
Proxy: webhookProxy(),
|
||||||
DialContext: hostmatcher.NewDialContextWithProxy("webhook", allowedHostMatcher, nil, setting.Webhook.ProxyURLFixed),
|
DialContext: hostmatcher.NewDialContext("webhook", allowedHostMatcher, nil),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,72 +14,35 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||||
"code.gitea.io/gitea/modules/hostmatcher"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWebhookProxy(t *testing.T) {
|
func TestWebhookProxy(t *testing.T) {
|
||||||
oldWebhook := setting.Webhook
|
|
||||||
t.Cleanup(func() {
|
|
||||||
setting.Webhook = oldWebhook
|
|
||||||
})
|
|
||||||
|
|
||||||
setting.Webhook.ProxyURL = "http://localhost:8080"
|
setting.Webhook.ProxyURL = "http://localhost:8080"
|
||||||
setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
|
setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
|
||||||
setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}
|
setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}
|
||||||
|
|
||||||
allowedHostMatcher := hostmatcher.ParseHostMatchList("webhook.ALLOWED_HOST_LIST", "discordapp.com,s.discordapp.com")
|
kases := map[string]string{
|
||||||
|
"https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx": "http://localhost:8080",
|
||||||
tests := []struct {
|
"http://s.discordapp.com/assets/xxxxxx": "http://localhost:8080",
|
||||||
req string
|
"http://github.com/a/b": "",
|
||||||
want string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
req: "https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx",
|
|
||||||
want: "http://localhost:8080",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
req: "http://s.discordapp.com/assets/xxxxxx",
|
|
||||||
want: "http://localhost:8080",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
req: "http://github.com/a/b",
|
|
||||||
want: "",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
req: "http://www.discordapp.com/assets/xxxxxx",
|
|
||||||
want: "",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.req, func(t *testing.T) {
|
|
||||||
req, err := http.NewRequest("POST", tt.req, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
u, err := webhookProxy(allowedHostMatcher)(req)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for reqURL, proxyURL := range kases {
|
||||||
|
req, err := http.NewRequest("POST", reqURL, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
got := ""
|
u, err := webhookProxy()(req)
|
||||||
if u != nil {
|
assert.NoError(t, err)
|
||||||
got = u.String()
|
if proxyURL == "" {
|
||||||
|
assert.Nil(t, u)
|
||||||
|
} else {
|
||||||
|
assert.EqualValues(t, proxyURL, u.String())
|
||||||
}
|
}
|
||||||
assert.Equal(t, tt.want, got)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,12 +173,6 @@ func (d *DingtalkPayload) Release(p *api.ReleasePayload) (api.Payloader, error)
|
||||||
return createDingtalkPayload(text, text, "view release", p.Release.HTMLURL), nil
|
return createDingtalkPayload(text, text, "view release", p.Release.HTMLURL), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DingtalkPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true)
|
|
||||||
|
|
||||||
return createDingtalkPayload(text, text, "view package", p.Package.HTMLURL), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDingtalkPayload(title, text, singleTitle, singleURL string) *DingtalkPayload {
|
func createDingtalkPayload(title, text, singleTitle, singleURL string) *DingtalkPayload {
|
||||||
return &DingtalkPayload{
|
return &DingtalkPayload{
|
||||||
MsgType: "actionCard",
|
MsgType: "actionCard",
|
||||||
|
|
|
@ -256,12 +256,6 @@ func (d *DiscordPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
|
||||||
return d.createPayload(p.Sender, text, p.Release.Note, p.Release.HTMLURL, color), nil
|
return d.createPayload(p.Sender, text, p.Release.Note, p.Release.HTMLURL, color), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DiscordPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
text, color := getPackagePayloadInfo(p, noneLinkFormatter, false)
|
|
||||||
|
|
||||||
return d.createPayload(p.Sender, text, "", p.Package.HTMLURL, color), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDiscordPayload converts a discord webhook into a DiscordPayload
|
// GetDiscordPayload converts a discord webhook into a DiscordPayload
|
||||||
func GetDiscordPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
|
func GetDiscordPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
|
||||||
s := new(DiscordPayload)
|
s := new(DiscordPayload)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
type (
|
type (
|
||||||
// FeishuPayload represents
|
// FeishuPayload represents
|
||||||
FeishuPayload struct {
|
FeishuPayload struct {
|
||||||
MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive / file /audio / media
|
MsgType string `json:"msg_type"` // text / post / image / share_chat / interactive
|
||||||
Content struct {
|
Content struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
} `json:"content"`
|
} `json:"content"`
|
||||||
|
@ -158,12 +158,6 @@ func (f *FeishuPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
|
||||||
return newFeishuTextPayload(text), nil
|
return newFeishuTextPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeishuPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true)
|
|
||||||
|
|
||||||
return newFeishuTextPayload(text), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFeishuPayload converts a ding talk webhook into a FeishuPayload
|
// GetFeishuPayload converts a ding talk webhook into a FeishuPayload
|
||||||
func GetFeishuPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
func GetFeishuPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
||||||
return convertPayloader(new(FeishuPayload), p, event)
|
return convertPayloader(new(FeishuPayload), p, event)
|
||||||
|
|
|
@ -230,24 +230,6 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo
|
||||||
return text, issueTitle, color
|
return text, issueTitle, color
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
|
||||||
refLink := linkFormatter(p.Package.HTMLURL, p.Package.Name+":"+p.Package.Version)
|
|
||||||
|
|
||||||
switch p.Action {
|
|
||||||
case api.HookPackageCreated:
|
|
||||||
text = fmt.Sprintf("Package created: %s", refLink)
|
|
||||||
color = greenColor
|
|
||||||
case api.HookPackageDeleted:
|
|
||||||
text = fmt.Sprintf("Package deleted: %s", refLink)
|
|
||||||
color = redColor
|
|
||||||
}
|
|
||||||
if withSender {
|
|
||||||
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName))
|
|
||||||
}
|
|
||||||
|
|
||||||
return text, color
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHook convert models.Webhook to api.Hook
|
// ToHook convert models.Webhook to api.Hook
|
||||||
// This function is not part of the convert package to prevent an import cycle
|
// This function is not part of the convert package to prevent an import cycle
|
||||||
func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
|
func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
|
||||||
|
|
|
@ -210,21 +210,6 @@ func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err
|
||||||
return getMatrixPayload(text, nil, m.MsgType), nil
|
return getMatrixPayload(text, nil, m.MsgType), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MatrixPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
|
||||||
repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
|
|
||||||
var text string
|
|
||||||
|
|
||||||
switch p.Action {
|
|
||||||
case api.HookPackageCreated:
|
|
||||||
text = fmt.Sprintf("[%s] Package published by %s", repoLink, senderLink)
|
|
||||||
case api.HookPackageDeleted:
|
|
||||||
text = fmt.Sprintf("[%s] Package deleted by %s", repoLink, senderLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMatrixPayload(text, nil, m.MsgType), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMatrixPayload converts a Matrix webhook into a MatrixPayload
|
// GetMatrixPayload converts a Matrix webhook into a MatrixPayload
|
||||||
func GetMatrixPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
|
func GetMatrixPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
|
||||||
s := new(MatrixPayload)
|
s := new(MatrixPayload)
|
||||||
|
|
|
@ -296,20 +296,6 @@ func (m *MSTeamsPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MSTeamsPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
title, color := getPackagePayloadInfo(p, noneLinkFormatter, false)
|
|
||||||
|
|
||||||
return createMSTeamsPayload(
|
|
||||||
p.Repository,
|
|
||||||
p.Sender,
|
|
||||||
title,
|
|
||||||
"",
|
|
||||||
p.Package.HTMLURL,
|
|
||||||
color,
|
|
||||||
&MSTeamsFact{"Package:", p.Package.Name},
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload
|
// GetMSTeamsPayload converts a MSTeams webhook into a MSTeamsPayload
|
||||||
func GetMSTeamsPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
func GetMSTeamsPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
||||||
return convertPayloader(new(MSTeamsPayload), p, event)
|
return convertPayloader(new(MSTeamsPayload), p, event)
|
||||||
|
|
|
@ -104,10 +104,6 @@ func (f *PackagistPayload) Release(_ *api.ReleasePayload) (api.Payloader, error)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *PackagistPayload) Package(_ *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPackagistPayload converts a packagist webhook into a PackagistPayload
|
// GetPackagistPayload converts a packagist webhook into a PackagistPayload
|
||||||
func GetPackagistPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
|
func GetPackagistPayload(p api.Payloader, event webhook_module.HookEventType, meta string) (api.Payloader, error) {
|
||||||
s := new(PackagistPayload)
|
s := new(PackagistPayload)
|
||||||
|
|
|
@ -22,7 +22,6 @@ type PayloadConvertor interface {
|
||||||
Repository(*api.RepositoryPayload) (api.Payloader, error)
|
Repository(*api.RepositoryPayload) (api.Payloader, error)
|
||||||
Release(*api.ReleasePayload) (api.Payloader, error)
|
Release(*api.ReleasePayload) (api.Payloader, error)
|
||||||
Wiki(*api.WikiPayload) (api.Payloader, error)
|
Wiki(*api.WikiPayload) (api.Payloader, error)
|
||||||
Package(*api.PackagePayload) (api.Payloader, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module.HookEventType) (api.Payloader, error) {
|
func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module.HookEventType) (api.Payloader, error) {
|
||||||
|
@ -54,8 +53,6 @@ func convertPayloader(s PayloadConvertor, p api.Payloader, event webhook_module.
|
||||||
return s.Release(p.(*api.ReleasePayload))
|
return s.Release(p.(*api.ReleasePayload))
|
||||||
case webhook_module.HookEventWiki:
|
case webhook_module.HookEventWiki:
|
||||||
return s.Wiki(p.(*api.WikiPayload))
|
return s.Wiki(p.(*api.WikiPayload))
|
||||||
case webhook_module.HookEventPackage:
|
|
||||||
return s.Package(p.(*api.PackagePayload))
|
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,12 +171,6 @@ func (s *SlackPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
|
||||||
return s.createPayload(text, nil), nil
|
return s.createPayload(text, nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SlackPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
text, _ := getPackagePayloadInfo(p, SlackLinkFormatter, true)
|
|
||||||
|
|
||||||
return s.createPayload(text, nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push implements PayloadConvertor Push method
|
// Push implements PayloadConvertor Push method
|
||||||
func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
func (s *SlackPayload) Push(p *api.PushPayload) (api.Payloader, error) {
|
||||||
// n new commits
|
// n new commits
|
||||||
|
|
|
@ -186,12 +186,6 @@ func (t *TelegramPayload) Release(p *api.ReleasePayload) (api.Payloader, error)
|
||||||
return createTelegramPayload(text), nil
|
return createTelegramPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TelegramPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
text, _ := getPackagePayloadInfo(p, htmlLinkFormatter, true)
|
|
||||||
|
|
||||||
return createTelegramPayload(text), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTelegramPayload converts a telegram webhook into a TelegramPayload
|
// GetTelegramPayload converts a telegram webhook into a TelegramPayload
|
||||||
func GetTelegramPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
func GetTelegramPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
||||||
return convertPayloader(new(TelegramPayload), p, event)
|
return convertPayloader(new(TelegramPayload), p, event)
|
||||||
|
|
|
@ -179,12 +179,6 @@ func (f *WechatworkPayload) Release(p *api.ReleasePayload) (api.Payloader, error
|
||||||
return newWechatworkMarkdownPayload(text), nil
|
return newWechatworkMarkdownPayload(text), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *WechatworkPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
|
||||||
text, _ := getPackagePayloadInfo(p, noneLinkFormatter, true)
|
|
||||||
|
|
||||||
return newWechatworkMarkdownPayload(text), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWechatworkPayload GetWechatworkPayload converts a ding talk webhook into a WechatworkPayload
|
// GetWechatworkPayload GetWechatworkPayload converts a ding talk webhook into a WechatworkPayload
|
||||||
func GetWechatworkPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
func GetWechatworkPayload(p api.Payloader, event webhook_module.HookEventType, _ string) (api.Payloader, error) {
|
||||||
return convertPayloader(new(WechatworkPayload), p, event)
|
return convertPayloader(new(WechatworkPayload), p, event)
|
||||||
|
|
|
@ -325,6 +325,8 @@
|
||||||
<!-- Environment Config -->
|
<!-- Environment Config -->
|
||||||
<h4 class="ui dividing header">{{.locale.Tr "install.env_config_keys"}}</h4>
|
<h4 class="ui dividing header">{{.locale.Tr "install.env_config_keys"}}</h4>
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
|
<label></label>
|
||||||
|
<button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>
|
||||||
<div class="right-content">
|
<div class="right-content">
|
||||||
{{.locale.Tr "install.env_config_keys_prompt"}}
|
{{.locale.Tr "install.env_config_keys_prompt"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
4
templates/swagger/v1_json.tmpl
generated
4
templates/swagger/v1_json.tmpl
generated
|
@ -20249,10 +20249,6 @@
|
||||||
"creator": {
|
"creator": {
|
||||||
"$ref": "#/definitions/User"
|
"$ref": "#/definitions/User"
|
||||||
},
|
},
|
||||||
"html_url": {
|
|
||||||
"type": "string",
|
|
||||||
"x-go-name": "HTMLURL"
|
|
||||||
},
|
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
|
|
|
@ -35,14 +35,6 @@ func TestAPIGetCommentAttachment(t *testing.T) {
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID})
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
||||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
session := loginUser(t, repoOwner.Name)
|
session := loginUser(t, repoOwner.Name)
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
|
||||||
|
|
|
@ -174,29 +174,15 @@ func TestAPIGetSystemUserComment(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIEditComment(t *testing.T) {
|
func TestAPIEditComment(t *testing.T) {
|
||||||
defer tests.AddFixtures("tests/integration/fixtures/TestAPIComment/")()
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
const newCommentBody = "This is the new comment body"
|
const newCommentBody = "This is the new comment body"
|
||||||
|
|
||||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1008},
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
|
||||||
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
||||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
||||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
||||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
|
||||||
repoOwner.Name, repo.Name, comment.ID, token)
|
|
||||||
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
|
|
||||||
"body": newCommentBody,
|
|
||||||
})
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||||
repoOwner.Name, repo.Name, comment.ID, token)
|
repoOwner.Name, repo.Name, comment.ID, token)
|
||||||
|
@ -213,25 +199,14 @@ func TestAPIEditComment(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIDeleteComment(t *testing.T) {
|
func TestAPIDeleteComment(t *testing.T) {
|
||||||
defer tests.AddFixtures("tests/integration/fixtures/TestAPIComment/")()
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1008},
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
|
||||||
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
||||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
||||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
||||||
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
|
||||||
repoOwner.Name, repo.Name, comment.ID, token)
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||||
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||||
repoOwner.Name, repo.Name, comment.ID, token)
|
repoOwner.Name, repo.Name, comment.ID, token)
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -108,27 +107,6 @@ func TestAPICommentReactions(t *testing.T) {
|
||||||
})
|
})
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
|
||||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
||||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
||||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s",
|
|
||||||
repoOwner.Name, repo.Name, comment.ID, token)
|
|
||||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
|
||||||
Reaction: "+1",
|
|
||||||
})
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
|
|
||||||
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
|
|
||||||
Reaction: "+1",
|
|
||||||
})
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
|
|
||||||
req = NewRequestf(t, "GET", urlStr)
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add allowed reaction
|
// Add allowed reaction
|
||||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
||||||
Reaction: "+1",
|
Reaction: "+1",
|
||||||
|
|
|
@ -72,17 +72,6 @@ func TestCreateReadOnlyDeployKey(t *testing.T) {
|
||||||
Content: rawKeyBody.Key,
|
Content: rawKeyBody.Key,
|
||||||
Mode: perm.AccessModeRead,
|
Mode: perm.AccessModeRead,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Using the ID of a key that does not belong to the repository must fail
|
|
||||||
{
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d?token=%s", repoOwner.Name, repo.Name, newDeployKey.ID, token))
|
|
||||||
MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
session5 := loginUser(t, "user5")
|
|
||||||
token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository)
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d?token=%s", newDeployKey.ID, token5))
|
|
||||||
MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateReadWriteDeployKey(t *testing.T) {
|
func TestCreateReadWriteDeployKey(t *testing.T) {
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package integration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/tests"
|
|
||||||
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAPITwoFactor(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16})
|
|
||||||
|
|
||||||
req := NewRequestf(t, "GET", "/api/v1/user")
|
|
||||||
req = AddBasicAuthHeader(req, user.Name)
|
|
||||||
MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
otpKey, err := totp.Generate(totp.GenerateOpts{
|
|
||||||
SecretSize: 40,
|
|
||||||
Issuer: "gitea-test",
|
|
||||||
AccountName: user.Name,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
tfa := &auth_model.TwoFactor{
|
|
||||||
UID: user.ID,
|
|
||||||
}
|
|
||||||
assert.NoError(t, tfa.SetSecret(otpKey.Secret()))
|
|
||||||
|
|
||||||
assert.NoError(t, auth_model.NewTwoFactor(tfa))
|
|
||||||
|
|
||||||
req = NewRequestf(t, "GET", "/api/v1/user")
|
|
||||||
req = AddBasicAuthHeader(req, user.Name)
|
|
||||||
MakeRequest(t, req, http.StatusUnauthorized)
|
|
||||||
|
|
||||||
passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
req = NewRequestf(t, "GET", "/api/v1/user")
|
|
||||||
req = AddBasicAuthHeader(req, user.Name)
|
|
||||||
req.Header.Set("X-Gitea-OTP", passcode)
|
|
||||||
MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
req = NewRequestf(t, "GET", "/api/v1/user")
|
|
||||||
req = AddBasicAuthHeader(req, user.Name)
|
|
||||||
req.Header.Set("X-Forgejo-OTP", passcode)
|
|
||||||
MakeRequest(t, req, http.StatusOK)
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
|
|
||||||
-
|
|
||||||
id: 1008
|
|
||||||
type: 0 # comment
|
|
||||||
poster_id: 2
|
|
||||||
issue_id: 4 # in repo_id 2
|
|
||||||
content: "comment in private pository"
|
|
||||||
created_unix: 946684811
|
|
||||||
updated_unix: 946684811
|
|
|
@ -205,111 +205,6 @@ func TestIssueCommentClose(t *testing.T) {
|
||||||
assert.Equal(t, "Description", val)
|
assert.Equal(t, "Description", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssueCommentDelete(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
session := loginUser(t, "user2")
|
|
||||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
|
|
||||||
comment1 := "Test comment 1"
|
|
||||||
commentID := testIssueAddComment(t, session, issueURL, comment1, "")
|
|
||||||
|
|
||||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
|
||||||
assert.Equal(t, comment1, comment.Content)
|
|
||||||
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, issueURL),
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
|
|
||||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, issueURL),
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: commentID})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssueCommentAttachment(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
const repoURL = "user2/repo1"
|
|
||||||
const content = "Test comment 4"
|
|
||||||
const status = ""
|
|
||||||
session := loginUser(t, "user2")
|
|
||||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
|
|
||||||
|
|
||||||
req := NewRequest(t, "GET", issueURL)
|
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
||||||
link, exists := htmlDoc.doc.Find("#comment-form").Attr("action")
|
|
||||||
assert.True(t, exists, "The template has changed")
|
|
||||||
|
|
||||||
uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK)
|
|
||||||
|
|
||||||
commentCount := htmlDoc.doc.Find(".comment-list .comment .render-content").Length()
|
|
||||||
|
|
||||||
req = NewRequestWithValues(t, "POST", link, map[string]string{
|
|
||||||
"_csrf": htmlDoc.GetCSRF(),
|
|
||||||
"content": content,
|
|
||||||
"status": status,
|
|
||||||
"files": uuid,
|
|
||||||
})
|
|
||||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
|
||||||
|
|
||||||
req = NewRequest(t, "GET", test.RedirectURL(resp))
|
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
|
||||||
|
|
||||||
val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text()
|
|
||||||
assert.Equal(t, content, val)
|
|
||||||
|
|
||||||
idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id")
|
|
||||||
idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:]
|
|
||||||
assert.True(t, has)
|
|
||||||
id, err := strconv.Atoi(idStr)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEqual(t, 0, id)
|
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/comments/%d/attachments", "user2", "repo1", id))
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/comments/%d/attachments", "user5", "repo4", id))
|
|
||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssueCommentUpdate(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
session := loginUser(t, "user2")
|
|
||||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
|
|
||||||
comment1 := "Test comment 1"
|
|
||||||
commentID := testIssueAddComment(t, session, issueURL, comment1, "")
|
|
||||||
|
|
||||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
|
||||||
assert.Equal(t, comment1, comment.Content)
|
|
||||||
|
|
||||||
modifiedContent := comment.Content + "MODIFIED"
|
|
||||||
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user5", "repo4", commentID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, issueURL),
|
|
||||||
"content": modifiedContent,
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
commentIdentical := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
|
||||||
assert.Equal(t, comment1, commentIdentical.Content)
|
|
||||||
|
|
||||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, issueURL),
|
|
||||||
"content": modifiedContent,
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
|
||||||
assert.Equal(t, modifiedContent, comment.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIssueReaction(t *testing.T) {
|
func TestIssueReaction(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
@ -661,45 +556,3 @@ func TestUpdateIssueDeadline(t *testing.T) {
|
||||||
|
|
||||||
assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
|
assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssuePinMove(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
session := loginUser(t, "user2")
|
|
||||||
issueURL, issue := testIssueWithBean(t, "user2", 1, "Title", "Content")
|
|
||||||
assert.EqualValues(t, 0, issue.PinOrder)
|
|
||||||
|
|
||||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/pin", issueURL), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, issueURL),
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
|
||||||
issue = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
|
|
||||||
|
|
||||||
position := 1
|
|
||||||
assert.EqualValues(t, position, issue.PinOrder)
|
|
||||||
|
|
||||||
newPosition := 2
|
|
||||||
|
|
||||||
// Using the ID of an issue that does not belong to the repository must fail
|
|
||||||
{
|
|
||||||
session5 := loginUser(t, "user5")
|
|
||||||
movePinURL := "/user5/repo4/issues/move_pin?_csrf=" + GetCSRF(t, session5, issueURL)
|
|
||||||
req = NewRequestWithJSON(t, "POST", movePinURL, map[string]any{
|
|
||||||
"id": issue.ID,
|
|
||||||
"position": newPosition,
|
|
||||||
})
|
|
||||||
session5.MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
|
|
||||||
issue = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
|
|
||||||
assert.EqualValues(t, position, issue.PinOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
movePinURL := issueURL[:strings.LastIndexByte(issueURL, '/')] + "/move_pin?_csrf=" + GetCSRF(t, session, issueURL)
|
|
||||||
req = NewRequestWithJSON(t, "POST", movePinURL, map[string]any{
|
|
||||||
"id": issue.ID,
|
|
||||||
"position": newPosition,
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusNoContent)
|
|
||||||
|
|
||||||
issue = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
|
|
||||||
assert.EqualValues(t, newPosition, issue.PinOrder)
|
|
||||||
}
|
|
||||||
|
|
|
@ -89,44 +89,6 @@ func TestCreateRelease(t *testing.T) {
|
||||||
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4)
|
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteRelease(t *testing.T) {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
|
||||||
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 57, OwnerName: "user2", LowerName: "repo-release"})
|
|
||||||
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{TagName: "v2.0"})
|
|
||||||
assert.False(t, release.IsTag)
|
|
||||||
|
|
||||||
// Using the ID of a comment that does not belong to the repository must fail
|
|
||||||
session5 := loginUser(t, "user5")
|
|
||||||
otherRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user5", LowerName: "repo4"})
|
|
||||||
|
|
||||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/releases/delete?id=%d", otherRepo.Link(), release.ID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session5, otherRepo.Link()),
|
|
||||||
})
|
|
||||||
session5.MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
|
||||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/releases/delete?id=%d", repo.Link(), release.ID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, repo.Link()),
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
release = unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: release.ID})
|
|
||||||
|
|
||||||
if assert.True(t, release.IsTag) {
|
|
||||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/tags/delete?id=%d", otherRepo.Link(), release.ID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session5, otherRepo.Link()),
|
|
||||||
})
|
|
||||||
session5.MakeRequest(t, req, http.StatusNotFound)
|
|
||||||
|
|
||||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/tags/delete?id=%d", repo.Link(), release.ID), map[string]string{
|
|
||||||
"_csrf": GetCSRF(t, session, repo.Link()),
|
|
||||||
})
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
unittest.AssertNotExistsBean(t, &repo_model.Release{ID: release.ID})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateReleasePreRelease(t *testing.T) {
|
func TestCreateReleasePreRelease(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
|
@ -264,13 +264,3 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
|
||||||
func Printf(format string, args ...any) {
|
func Printf(format string, args ...any) {
|
||||||
testlogger.Printf(format, args...)
|
testlogger.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddFixtures(dirs ...string) func() {
|
|
||||||
return unittest.OverrideFixtures(
|
|
||||||
unittest.FixturesOptions{
|
|
||||||
Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
|
|
||||||
Base: filepath.Dir(setting.AppPath),
|
|
||||||
Dirs: dirs,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,11 +4,9 @@ import {displayError} from './common.js';
|
||||||
|
|
||||||
const {mermaidMaxSourceCharacters} = window.config;
|
const {mermaidMaxSourceCharacters} = window.config;
|
||||||
|
|
||||||
// margin removal is for https://github.com/mermaid-js/mermaid/issues/4907
|
|
||||||
const iframeCss = `:root {color-scheme: normal}
|
const iframeCss = `:root {color-scheme: normal}
|
||||||
body {margin: 0; padding: 0; overflow: hidden}
|
body {margin: 0; padding: 0; overflow: hidden}
|
||||||
#mermaid {display: block; margin: 0 auto}
|
#mermaid {display: block; margin: 0 auto}`;
|
||||||
blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {margin: 0}`;
|
|
||||||
|
|
||||||
export async function renderMermaid() {
|
export async function renderMermaid() {
|
||||||
const els = document.querySelectorAll('.markup code.language-mermaid');
|
const els = document.querySelectorAll('.markup code.language-mermaid');
|
||||||
|
|
Loading…
Reference in a new issue