mirror of
https://github.com/go-gitea/gitea
synced 2026-02-10 18:08:11 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f831540ebd | ||
|
|
1e9a7656e0 | ||
|
|
088d746636 | ||
|
|
c208b1180e | ||
|
|
dfd511faf3 | ||
|
|
41b2d0be93 | ||
|
|
4d5854b216 | ||
|
|
439e071acf | ||
|
|
69ea554e23 | ||
|
|
c077a084d7 | ||
|
|
dfd960f22a | ||
|
|
2978b435bb | ||
|
|
00705da102 | ||
|
|
4a48370d91 | ||
|
|
1dedf9bba0 | ||
|
|
2147bfde05 | ||
|
|
a1c232cae3 | ||
|
|
63512cd15d | ||
|
|
71803d33e3 | ||
|
|
a954cc3fb9 | ||
|
|
ca4418eff1 | ||
|
|
80c0c88152 | ||
|
|
171950a0d4 | ||
|
|
7b96f71bc7 | ||
|
|
e6d1afaee3 | ||
|
|
1d4c193df5 | ||
|
|
4ffa683820 | ||
|
|
973b7f6298 | ||
|
|
4126aad4aa | ||
|
|
5c96a2be87 | ||
|
|
acedf0f702 | ||
|
|
23139aa27b | ||
|
|
b6b71c78c4 | ||
|
|
2138661dae | ||
|
|
4b37eb2c23 | ||
|
|
dd2f007501 | ||
|
|
dd44c2164e | ||
|
|
2604571993 | ||
|
|
eae6985b63 | ||
|
|
d8583edfe7 | ||
|
|
d99479c810 | ||
|
|
fbe1f35112 | ||
|
|
25233a9bdc | ||
|
|
7a99c7b83c | ||
|
|
1d6e5c8e58 | ||
|
|
882e465c3a | ||
|
|
b139234fa8 | ||
|
|
d8b39324d7 | ||
|
|
9df573bddc | ||
|
|
b0a405c5fa | ||
|
|
3c53740244 | ||
|
|
da7d7e60d8 |
+12
-16
@@ -13,46 +13,42 @@ groups:
|
||||
-
|
||||
name: BREAKING
|
||||
labels:
|
||||
- kind/breaking
|
||||
- pr/breaking
|
||||
-
|
||||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
- topic/security
|
||||
-
|
||||
name: FEATURES
|
||||
labels:
|
||||
- kind/feature
|
||||
- type/feature
|
||||
-
|
||||
name: API
|
||||
labels:
|
||||
- kind/api
|
||||
- modifies/api
|
||||
-
|
||||
name: ENHANCEMENTS
|
||||
labels:
|
||||
- kind/enhancement
|
||||
- kind/refactor
|
||||
- kind/ui
|
||||
- type/enhancement
|
||||
- type/refactoring
|
||||
- topic/ui
|
||||
-
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
- type/bug
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
- kind/testing
|
||||
-
|
||||
name: TRANSLATION
|
||||
labels:
|
||||
- kind/translation
|
||||
- type/testing
|
||||
-
|
||||
name: BUILD
|
||||
labels:
|
||||
- kind/build
|
||||
- kind/lint
|
||||
- topic/build
|
||||
- topic/code-linting
|
||||
-
|
||||
name: DOCS
|
||||
labels:
|
||||
- kind/docs
|
||||
- type/docs
|
||||
-
|
||||
name: MISC
|
||||
default: true
|
||||
|
||||
-426
@@ -1,426 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: release-version
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
workspace:
|
||||
base: /source
|
||||
path: /
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
volumes:
|
||||
- name: deps
|
||||
temp: {}
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
pull: always
|
||||
commands:
|
||||
- git fetch --tags --force
|
||||
|
||||
- name: deps-frontend
|
||||
image: node:20
|
||||
pull: always
|
||||
commands:
|
||||
- make deps-frontend
|
||||
|
||||
- name: deps-backend
|
||||
image: gitea/test_env:linux-1.20-amd64
|
||||
pull: always
|
||||
commands:
|
||||
- make deps-backend
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
|
||||
- name: static
|
||||
image: techknowlogick/xgo:go-1.20.x
|
||||
pull: always
|
||||
commands:
|
||||
# Upgrade to node 20 once https://github.com/techknowlogick/xgo/issues/163 is resolved
|
||||
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get -qqy install nodejs
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release
|
||||
environment:
|
||||
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
depends_on: [fetch-tags]
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go
|
||||
|
||||
- name: gpg-sign
|
||||
image: plugins/gpgsign:1
|
||||
pull: always
|
||||
settings:
|
||||
detach_sign: true
|
||||
excludes:
|
||||
- "dist/release/*.sha256"
|
||||
files:
|
||||
- "dist/release/*"
|
||||
environment:
|
||||
GPGSIGN_KEY:
|
||||
from_secret: gpgsign_key
|
||||
GPGSIGN_PASSPHRASE:
|
||||
from_secret: gpgsign_passphrase
|
||||
depends_on: [static]
|
||||
|
||||
- name: release-tag
|
||||
image: woodpeckerci/plugin-s3:latest
|
||||
pull: always
|
||||
settings:
|
||||
acl:
|
||||
from_secret: aws_s3_acl
|
||||
region:
|
||||
from_secret: aws_s3_region
|
||||
bucket:
|
||||
from_secret: aws_s3_bucket
|
||||
endpoint:
|
||||
from_secret: aws_s3_endpoint
|
||||
path_style:
|
||||
from_secret: aws_s3_path_style
|
||||
source: "dist/release/*"
|
||||
strip_prefix: dist/release/
|
||||
target: "/gitea/${DRONE_TAG##v}"
|
||||
environment:
|
||||
AWS_ACCESS_KEY_ID:
|
||||
from_secret: aws_access_key_id
|
||||
AWS_SECRET_ACCESS_KEY:
|
||||
from_secret: aws_secret_access_key
|
||||
depends_on: [gpg-sign]
|
||||
|
||||
- name: github
|
||||
image: plugins/github-release:latest
|
||||
pull: always
|
||||
settings:
|
||||
files:
|
||||
- "dist/release/*"
|
||||
file_exists: overwrite
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
depends_on: [gpg-sign]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-linux-amd64-release-version
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
include:
|
||||
- "refs/tags/**"
|
||||
exclude:
|
||||
- "refs/tags/**-rc*"
|
||||
paths:
|
||||
exclude:
|
||||
- "docs/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
pull: always
|
||||
commands:
|
||||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-linux-amd64-release-candidate-version
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- "refs/tags/**-rc*"
|
||||
paths:
|
||||
exclude:
|
||||
- "docs/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
pull: always
|
||||
commands:
|
||||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
tags: ${DRONE_TAG##v}-linux-amd64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
tags: ${DRONE_TAG##v}-linux-amd64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-linux-arm64-release-version
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
include:
|
||||
- "refs/tags/**"
|
||||
exclude:
|
||||
- "refs/tags/**-rc*"
|
||||
paths:
|
||||
exclude:
|
||||
- "docs/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
pull: always
|
||||
commands:
|
||||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-linux-arm64-release-candidate-version
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- "refs/tags/**-rc*"
|
||||
paths:
|
||||
exclude:
|
||||
- "docs/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
pull: always
|
||||
commands:
|
||||
- git fetch --tags --force
|
||||
|
||||
- name: publish
|
||||
image: plugins/docker:latest
|
||||
pull: always
|
||||
settings:
|
||||
tags: ${DRONE_TAG##v}-linux-arm64
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
- name: publish-rootless
|
||||
image: plugins/docker:latest
|
||||
settings:
|
||||
dockerfile: Dockerfile.rootless
|
||||
tags: ${DRONE_TAG##v}-linux-arm64-rootless
|
||||
repo: gitea/gitea
|
||||
build_args:
|
||||
- GOPROXY=https://goproxy.io
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
environment:
|
||||
PLUGIN_MIRROR:
|
||||
from_secret: plugin_mirror
|
||||
DOCKER_BUILDKIT: 1
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-manifest-version
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: manifest-rootless
|
||||
image: plugins/manifest
|
||||
pull: always
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: docker/manifest.rootless.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
||||
- name: manifest
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: docker/manifest.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
paths:
|
||||
exclude:
|
||||
- "docs/**"
|
||||
|
||||
depends_on:
|
||||
- docker-linux-amd64-release-version
|
||||
- docker-linux-amd64-release-candidate-version
|
||||
- docker-linux-arm64-release-version
|
||||
- docker-linux-arm64-release-candidate-version
|
||||
@@ -0,0 +1,5 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- actuated-4cpu-8gb
|
||||
- actuated-4cpu-16gb
|
||||
- nscloud
|
||||
@@ -0,0 +1,146 @@
|
||||
name: release-tag-version
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v1.*"
|
||||
- "!v1*-rc*"
|
||||
- "!v1*-dev"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
binary:
|
||||
runs-on: nscloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: make deps-frontend deps-backend
|
||||
# xgo build
|
||||
- run: make release
|
||||
env:
|
||||
TAGS: bindata sqlite sqlite_unlock_notify
|
||||
- name: import gpg key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
||||
- name: sign binaries
|
||||
run: |
|
||||
for f in dist/release/*; do
|
||||
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
|
||||
done
|
||||
# clean branch name to get the folder name in S3
|
||||
- name: Get cleaned branch name
|
||||
id: clean_name
|
||||
run: |
|
||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
||||
echo "Cleaned name is ${REF_NAME}"
|
||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||
- name: configure aws
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
- name: upload binaries to s3
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
- name: Install GH CLI
|
||||
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||
with:
|
||||
gh-cli-version: 2.39.1
|
||||
- name: create github release
|
||||
run: |
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
docker-rootful:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
with:
|
||||
images: gitea/gitea
|
||||
flavor: |
|
||||
latest=false
|
||||
# this will generate tags in the following format:
|
||||
# since it's not a main stable version, just generation 1.x and 1.x.x
|
||||
# latest
|
||||
# 1
|
||||
# 1.2
|
||||
# 1.2.3
|
||||
tags: |
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: build rootful docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
docker-rootless:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/metadata-action@v5
|
||||
id: meta
|
||||
with:
|
||||
images: gitea/gitea
|
||||
# each tag below will have the suffix of -rootless
|
||||
flavor: |
|
||||
latest=false
|
||||
suffix=-rootless
|
||||
# this will generate tags in the following format (with -rootless suffix added):
|
||||
# since it's not a main stable version, just generation 1.x and 1.x.x
|
||||
# latest
|
||||
# 1
|
||||
# 1.2
|
||||
# 1.2.3
|
||||
tags: |
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{version}}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: build rootless docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -4,6 +4,65 @@ 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
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.20.6](https://github.com/go-gitea/gitea/releases/tag/1.20.6) - 2023-11-26
|
||||
|
||||
* SECURITY
|
||||
* Fix comment permissions (#28213) (#28217)
|
||||
* Dont leak private users via extensions (#28023) (#28028)
|
||||
* Unify two factor check (#27915) (#27939)
|
||||
* Support allowed hosts for webhook to work with proxy (#27655) (#27674)
|
||||
* BUGFIXES
|
||||
* Fix no ActionTaskOutput table waring (#28149) (#28151)
|
||||
* Restricted users only see repos in orgs which their team was assigned to (#28025) (#28050)
|
||||
* Fix DownloadFunc when migrating releases (#27887) (#27889)
|
||||
* Fix http protocol auth (#27875) (#27878)
|
||||
* Revert "fix orphan check for deleted branch (#27310) (#27320)" (#27763)
|
||||
* Fix label render containing invalid HTML (#27752) (#27761)
|
||||
* Fix poster is not loaded in get default merge message (#27657) (#27665)
|
||||
* Fix 404 when deleting Docker package with an internal version (#27615) (#27629)
|
||||
* Fix attachment download bug (#27486) (#27570)
|
||||
* When comparing with an non-exist repository, return 404 but 500 (#27437) (#27441)
|
||||
* API
|
||||
* Fix package webhook (#27839) (#27854)
|
||||
* Fix org team endpoint (#27721) (#27729)
|
||||
* ENHANCEMENTS
|
||||
* Render email addresses as such if followed by punctuation (#27987) (#27991)
|
||||
* Fix mermaid flowchart margin issue (#27503) (#27517)
|
||||
* Fix panic in storageHandler (#27446) (#27478)
|
||||
* DOCS
|
||||
* Update agit-support.en-us.md (#27652)
|
||||
* MISC
|
||||
* Fix wrong xorm Delete usage(backport for 1.20) (#28003)
|
||||
* Remove duplicated button in Install web page (#27941)
|
||||
* Avoid run change title process when the title is same (#27467) (#27557)
|
||||
|
||||
## [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
|
||||
|
||||
* SECURITY
|
||||
|
||||
@@ -1724,8 +1724,8 @@ LEVEL = Info
|
||||
;; Session cookie name
|
||||
;COOKIE_NAME = i_like_gitea
|
||||
;;
|
||||
;; If you use session in https only, default is false
|
||||
;COOKIE_SECURE = false
|
||||
;; If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||
;COOKIE_SECURE =
|
||||
;;
|
||||
;; Session GC time interval in seconds, default is 86400 (1 day)
|
||||
;GC_INTERVAL_TIME = 86400
|
||||
|
||||
@@ -337,7 +337,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||
- `SSH_AUTHORIZED_PRINCIPALS_ALLOW`: **off** or **username, email**: \[off, username, email, anything\]: Specify the principals values that users are allowed to use as principal. When set to `anything` no checks are done on the principal string. When set to `off` authorized principal are not allowed to be set.
|
||||
- `SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE`: **false/true**: Gitea will create a authorized_principals file by default when it is not using the internal ssh server and `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`.
|
||||
- `SSH_AUTHORIZED_PRINCIPALS_BACKUP`: **false/true**: Enable SSH Authorized Principals Backup when rewriting all keys, default is true if `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`.
|
||||
- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}**: Set the template for the command to passed on authorized keys. Possible keys are: AppPath, AppWorkPath, CustomConf, CustomPath, Key - where Key is a `models/asymkey.PublicKey` and the others are strings which are shellquoted.
|
||||
- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **`{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}`**: Set the template for the command to passed on authorized keys. Possible keys are: AppPath, AppWorkPath, CustomConf, CustomPath, Key - where Key is a `models/asymkey.PublicKey` and the others are strings which are shellquoted.
|
||||
- `SSH_SERVER_CIPHERS`: **chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com**: For the built-in SSH server, choose the ciphers to support for SSH connections, for system SSH this setting has no effect.
|
||||
- `SSH_SERVER_KEY_EXCHANGES`: **curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1**: For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, for system SSH this setting has no effect.
|
||||
- `SSH_SERVER_MACS`: **hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1**: For the built-in SSH server, choose the MACs to support for SSH connections, for system SSH this setting has no effect
|
||||
@@ -443,7 +443,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||
- `ITERATE_BUFFER_SIZE`: **50**: Internal buffer size for iterating.
|
||||
- `CHARSET`: **utf8mb4**: For MySQL only, either "utf8" or "utf8mb4". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this.
|
||||
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
|
||||
- `LOG_SQL`: **true**: Log the executed SQL.
|
||||
- `LOG_SQL`: **false**: Log the executed SQL.
|
||||
- `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed.
|
||||
- `DB_RETRY_BACKOFF`: **3s**: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occurred.
|
||||
- `MAX_OPEN_CONNS` **0**: Database maximum open connections - default is 0, meaning there is no limit.
|
||||
@@ -772,7 +772,7 @@ and
|
||||
|
||||
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, redis-cluster, db, mysql, couchbase, memcache, postgres\]. Setting `db` will reuse the configuration in `[database]`
|
||||
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for db, empty (database config will be used); for others, the connection string. Relative paths will be made absolute against _`AppWorkPath`_.
|
||||
- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
|
||||
- `COOKIE_SECURE`:**_empty_**: `true` or `false`. Enable this to force using HTTPS for all session access. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
|
||||
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
|
||||
- `GC_INTERVAL_TIME`: **86400**: GC interval in seconds.
|
||||
- `SESSION_LIFE_TIME`: **86400**: Session life time in seconds, default is 86400 (1 day)
|
||||
@@ -1384,7 +1384,7 @@ Please note that using `self` is not recommended for most cases, as it could mak
|
||||
Additionally, it requires you to mirror all the actions you need to your Gitea instance, which may not be worth it.
|
||||
Therefore, please use `self` only if you understand what you are doing.
|
||||
|
||||
In earlier versions (<= 1.19), `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`.
|
||||
In earlier versions (`<= 1.19`), `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`.
|
||||
However, later updates removed those options, and now the only options are `github` and `self`, with the default value being `github`.
|
||||
However, if you want to use actions from other git server, you can use a complete URL in `uses` field, it's supported by Gitea (but not GitHub).
|
||||
Like `uses: https://gitea.com/actions/checkout@v3` or `uses: http://your-git-server/actions/checkout@v3`.
|
||||
|
||||
@@ -98,7 +98,7 @@ menu:
|
||||
- `SSL_MODE`: MySQL 或 PostgreSQL数据库是否启用SSL模式。
|
||||
- `CHARSET`: **utf8mb4**: 仅当数据库为 MySQL 时有效, 可以为 "utf8" 或 "utf8mb4"。注意:如果使用 "utf8mb4",你的 MySQL InnoDB 版本必须在 5.6 以上。
|
||||
- `PATH`: SQLite3 数据文件存放路径。
|
||||
- `LOG_SQL`: **true**: 显示生成的SQL,默认为真。
|
||||
- `LOG_SQL`: **false**: 显示生成的SQL,默认为真。
|
||||
- `MAX_IDLE_CONNS` **0**: 最大空闲数据库连接
|
||||
- `CONN_MAX_LIFETIME` **3s**: 数据库连接最大存活时间
|
||||
|
||||
@@ -200,7 +200,7 @@ menu:
|
||||
|
||||
- `PROVIDER`: Session 内容存储方式,可选 `memory`, `file`, `redis` 或 `mysql`。
|
||||
- `PROVIDER_CONFIG`: 如果是文件,那么这里填根目录;其他的要填主机地址和端口。
|
||||
- `COOKIE_SECURE`: 强制使用 HTTPS 作为session访问。
|
||||
- `COOKIE_SECURE`: **_empty_**:`true` 或 `false`。启用此选项以强制在所有会话访问中使用 HTTPS。如果没有设置,当 ROOT_URL 是 https 链接的时候默认设置为 true。
|
||||
- `GC_INTERVAL_TIME`: Session失效时间。
|
||||
|
||||
## Picture (`picture`)
|
||||
|
||||
@@ -55,7 +55,7 @@ PASSWD = `password`
|
||||
|
||||
要发送测试邮件以验证设置,请转到 Gitea > 站点管理 > 配置 > SMTP 邮件配置。
|
||||
|
||||
有关所有选项的完整列表,请查看[配置速查表](doc/administration/config-cheat-sheet.zh-cn.md)。
|
||||
有关所有选项的完整列表,请查看[配置速查表](administration/config-cheat-sheet.md)。
|
||||
|
||||
请注意:只有在使用 TLS 或 `HOST=localhost` 加密 SMTP 服务器通信时才支持身份验证。TLS 加密可以通过以下方式进行:
|
||||
|
||||
|
||||
@@ -138,9 +138,9 @@ All Gitea instances have the built-in API and there is no way to disable it comp
|
||||
You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`.
|
||||
For more information, refer to Gitea's [API docs](development/api-usage.md).
|
||||
|
||||
You can see the latest API (for example) on <https://try.gitea.io/api/swagger>.
|
||||
You can see the latest API (for example) on https://try.gitea.io/api/swagger
|
||||
|
||||
You can also see an example of the `swagger.json` file at <https://try.gitea.io/swagger.v1.json>.
|
||||
You can also see an example of the `swagger.json` file at https://try.gitea.io/swagger.v1.json
|
||||
|
||||
## Adjusting your server for public/private use
|
||||
|
||||
|
||||
@@ -142,9 +142,9 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供
|
||||
但是,您可以在app.ini的api部分将ENABLE_SWAGGER设置为false,以禁用其文档显示。
|
||||
有关更多信息,请参阅Gitea的[API文档](development/api-usage.md)。
|
||||
|
||||
您可以在上查看最新的API(例如)<https://try.gitea.io/api/swagger>。
|
||||
您可以在上查看最新的API(例如)https://try.gitea.io/api/swagger
|
||||
|
||||
您还可以在上查看`swagger.json`文件的示例 <https://try.gitea.io/swagger.v1.json>。
|
||||
您还可以在上查看`swagger.json`文件的示例 https://try.gitea.io/swagger.v1.json
|
||||
|
||||
## 调整服务器用于公共/私有使用
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ menu:
|
||||
|
||||
# Database Preparation
|
||||
|
||||
You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
|
||||
You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), MariaDB, SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
|
||||
|
||||
Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database).
|
||||
|
||||
Note: All steps below requires that the database engine of your choice is installed on your system. For remote database setup, install the server application on database instance and client program on your Gitea server. The client program is used to test connection to the database from Gitea server, while Gitea itself use database driver provided by Go to accomplish the same thing. In addition, make sure you use same engine version for both server and client for some engine features to work. For security reason, protect `root` (MySQL) or `postgres` (PostgreSQL) database superuser with secure password. The steps assumes that you run Linux for both database and Gitea servers.
|
||||
|
||||
## MySQL
|
||||
## MySQL/MariaDB
|
||||
|
||||
1. For remote database setup, you will need to make MySQL listen to your IP address. Edit `bind-address` option on `/etc/mysql/my.cnf` on database instance to:
|
||||
|
||||
@@ -45,7 +45,7 @@ Note: All steps below requires that the database engine of your choice is instal
|
||||
|
||||
```sql
|
||||
SET old_passwords=0;
|
||||
CREATE USER 'gitea' IDENTIFIED BY 'gitea';
|
||||
CREATE USER 'gitea'@'%' IDENTIFIED BY 'gitea';
|
||||
```
|
||||
|
||||
For remote database:
|
||||
|
||||
@@ -180,3 +180,6 @@ For events supported only by GitHub, see GitHub's [documentation](https://docs.g
|
||||
| pull_request_review_comment | `created`, `edited` |
|
||||
| release | `published`, `edited` |
|
||||
| registry_package | `published` |
|
||||
|
||||
> For `pull_request` events, in [GitHub Actions](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request), the `ref` is `refs/pull/:prNumber/merge`, which is a reference to the merge commit preview. However, Gitea has no such reference.
|
||||
> Therefore, the `ref` in Gitea Actions is `refs/pull/:prNumber/head`, which points to the head of pull request rather than the preview of the merge commit.
|
||||
|
||||
@@ -180,3 +180,6 @@ defaults:
|
||||
| pull_request_review_comment | `created`, `edited` |
|
||||
| release | `published`, `edited` |
|
||||
| registry_package | `published` |
|
||||
|
||||
> 对于 `pull_request` 事件,在 [GitHub Actions](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) 中 `ref` 是 `refs/pull/:prNumber/merge`,它指向这个拉取请求合并提交的一个预览。但是 Gitea 没有这种 reference。
|
||||
> 因此,Gitea Actions 中 `ref` 是 `refs/pull/:prNumber/head`,它指向这个拉取请求的头分支而不是合并提交的预览。
|
||||
|
||||
@@ -198,7 +198,7 @@ administrative user.
|
||||
field is set to `mail.com`, then Gitea will expect the `user email` field
|
||||
for an authenticated GIT instance to be `gituser@mail.com`.[^2]
|
||||
|
||||
**Note**: PAM support is added via [build-time flags](installation/install-from-source.md#build),
|
||||
**Note**: PAM support is added via [build-time flags](installation/from-source.md#build),
|
||||
and the official binaries provided do not have this enabled. PAM requires that
|
||||
the necessary libpam dynamic library be available and the necessary PAM
|
||||
development headers be accessible to the compiler.
|
||||
|
||||
@@ -20,6 +20,10 @@ type ActionTaskOutput struct {
|
||||
OutputValue string `xorm:"MEDIUMTEXT"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ActionTaskOutput))
|
||||
}
|
||||
|
||||
// FindTaskOutputByTaskID returns the outputs of the task.
|
||||
func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) {
|
||||
var outputs []*ActionTaskOutput
|
||||
|
||||
@@ -342,7 +342,7 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error
|
||||
|
||||
// Published releases list
|
||||
sess := releasesForActivityStatement(repoID, fromTime)
|
||||
sess.OrderBy("release.created_unix DESC")
|
||||
sess.OrderBy("`release`.created_unix DESC")
|
||||
stats.PublishedReleases = make([]*repo_model.Release, 0)
|
||||
if err = sess.Find(&stats.PublishedReleases); err != nil {
|
||||
return err
|
||||
@@ -350,7 +350,7 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error
|
||||
|
||||
// Published releases authors
|
||||
sess = releasesForActivityStatement(repoID, fromTime)
|
||||
if _, err = sess.Select("count(distinct release.publisher_id) as `count`").Table("release").Get(&count); err != nil {
|
||||
if _, err = sess.Select("count(distinct `release`.publisher_id) as `count`").Table("release").Get(&count); err != nil {
|
||||
return err
|
||||
}
|
||||
stats.PublishedReleaseAuthorCount = count
|
||||
@@ -359,7 +359,7 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error
|
||||
}
|
||||
|
||||
func releasesForActivityStatement(repoID int64, fromTime time.Time) *xorm.Session {
|
||||
return db.GetEngine(db.DefaultContext).Where("release.repo_id = ?", repoID).
|
||||
And("release.is_draft = ?", false).
|
||||
And("release.created_unix >= ?", fromTime.Unix())
|
||||
return db.GetEngine(db.DefaultContext).Where("`release`.repo_id = ?", repoID).
|
||||
And("`release`.is_draft = ?", false).
|
||||
And("`release`.created_unix >= ?", fromTime.Unix())
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@ func CountUserGPGKeys(userID int64) (int64, error) {
|
||||
}
|
||||
|
||||
// GetGPGKeyByID returns public key by given ID.
|
||||
func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
|
||||
func GetGPGKeyForUserByID(ownerID, keyID int64) (*GPGKey, error) {
|
||||
key := new(GPGKey)
|
||||
has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key)
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("id=? AND owner_id=?", keyID, ownerID).Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
@@ -225,7 +225,7 @@ func deleteGPGKey(ctx context.Context, keyID string) (int64, error) {
|
||||
|
||||
// DeleteGPGKey deletes GPG key information in database.
|
||||
func DeleteGPGKey(doer *user_model.User, id int64) (err error) {
|
||||
key, err := GetGPGKeyByID(id)
|
||||
key, err := GetGPGKeyForUserByID(doer.ID, id)
|
||||
if err != nil {
|
||||
if IsErrGPGKeyNotExist(err) {
|
||||
return nil
|
||||
@@ -233,11 +233,6 @@ func DeleteGPGKey(doer *user_model.User, id int64) (err error) {
|
||||
return fmt.Errorf("GetPublicKeyByID: %w", err)
|
||||
}
|
||||
|
||||
// Check if user has access to delete this key.
|
||||
if !doer.IsAdmin && doer.ID != key.OwnerID {
|
||||
return ErrGPGKeyAccessDenied{doer.ID, key.ID}
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -232,7 +232,7 @@ func CreateSource(source *Source) error {
|
||||
err = registerableSource.RegisterSource()
|
||||
if err != nil {
|
||||
// remove the AuthSource in case of errors while registering configuration
|
||||
if _, err := db.GetEngine(db.DefaultContext).Delete(source); err != nil {
|
||||
if _, err := db.GetEngine(db.DefaultContext).ID(source.ID).Delete(new(Source)); err != nil {
|
||||
log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,12 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
||||
return DefaultAvatarLink()
|
||||
}
|
||||
|
||||
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar,
|
||||
setting.GetDefaultDisableGravatar(),
|
||||
)
|
||||
|
||||
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar,
|
||||
setting.GetDefaultEnableFederatedAvatar(disableGravatar))
|
||||
|
||||
var err error
|
||||
if enableFederatedAvatar && system_model.LibravatarService != nil {
|
||||
@@ -174,7 +179,6 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
|
||||
return urlStr
|
||||
}
|
||||
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
if !disableGravatar {
|
||||
// copy GravatarSourceURL, because we will modify its Path.
|
||||
avatarURLCopy := *system_model.GravatarSourceURL
|
||||
|
||||
@@ -140,3 +140,16 @@
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 12
|
||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22
|
||||
repo_id: 2
|
||||
issue_id: 0
|
||||
release_id: 11
|
||||
uploader_id: 2
|
||||
comment_id: 0
|
||||
name: README.md
|
||||
download_count: 0
|
||||
size: 0
|
||||
created_unix: 946684800
|
||||
|
||||
@@ -66,3 +66,12 @@
|
||||
tree_path: "README.md"
|
||||
created_unix: 946684812
|
||||
invalidated: true
|
||||
|
||||
-
|
||||
id: 8
|
||||
type: 0 # comment
|
||||
poster_id: 2
|
||||
issue_id: 4 # in repo_id 2
|
||||
content: "comment in private pository"
|
||||
created_unix: 946684811
|
||||
updated_unix: 946684811
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
priority: 0
|
||||
is_closed: true
|
||||
is_pull: false
|
||||
num_comments: 0
|
||||
num_comments: 1
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
@@ -136,3 +136,17 @@
|
||||
is_prerelease: false
|
||||
is_tag: false
|
||||
created_unix: 946684803
|
||||
|
||||
- id: 11
|
||||
repo_id: 2
|
||||
publisher_id: 2
|
||||
tag_name: "v1.1"
|
||||
lower_tag_name: "v1.1"
|
||||
target: ""
|
||||
title: "v1.1"
|
||||
sha1: "205ac761f3326a7ebe416e8673760016450b5cec"
|
||||
num_commits: 2
|
||||
is_draft: false
|
||||
is_prerelease: false
|
||||
is_tag: false
|
||||
created_unix: 946684803
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-
|
||||
id: 1
|
||||
setting_key: 'disable_gravatar'
|
||||
setting_key: 'picture.disable_gravatar'
|
||||
setting_value: 'false'
|
||||
version: 1
|
||||
created: 1653533198
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
-
|
||||
id: 2
|
||||
setting_key: 'enable_federated_avatar'
|
||||
setting_key: 'picture.enable_federated_avatar'
|
||||
setting_value: 'false'
|
||||
version: 1
|
||||
created: 1653533198
|
||||
|
||||
@@ -1014,6 +1014,7 @@ type FindCommentsOptions struct {
|
||||
Type CommentType
|
||||
IssueIDs []int64
|
||||
Invalidated util.OptionalBool
|
||||
IsPull util.OptionalBool
|
||||
}
|
||||
|
||||
// ToConds implements FindOptions interface
|
||||
@@ -1048,6 +1049,9 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
|
||||
if !opts.Invalidated.IsNone() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1055,7 +1059,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
|
||||
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
|
||||
comments := make([]*Comment, 0, 10)
|
||||
sess := db.GetEngine(ctx).Where(opts.ToConds())
|
||||
if opts.RepoID > 0 {
|
||||
if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone {
|
||||
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// CommentList defines a list of comments
|
||||
@@ -422,37 +423,18 @@ func (comments CommentList) loadReviews(ctx context.Context) error {
|
||||
|
||||
reviewIDs := comments.getReviewIDs()
|
||||
reviews := make(map[int64]*Review, len(reviewIDs))
|
||||
left := len(reviewIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("id", reviewIDs[:limit]).
|
||||
Rows(new(Review))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var review Review
|
||||
err = rows.Scan(&review)
|
||||
if err != nil {
|
||||
_ = rows.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
reviews[review.ID] = &review
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
left -= limit
|
||||
reviewIDs = reviewIDs[limit:]
|
||||
if err := db.GetEngine(ctx).In("id", reviewIDs).Find(&reviews); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
comment.Review = reviews[comment.ReviewID]
|
||||
if comment.Review == nil {
|
||||
if comment.ReviewID > 0 {
|
||||
log.Error("comment with review id [%d] but has no review record", comment.ReviewID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If the comment dismisses a review, we need to load the reviewer to show whose review has been dismissed.
|
||||
// Otherwise, the reviewer is the poster of the comment, so we don't need to load it.
|
||||
|
||||
@@ -218,9 +218,9 @@ func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistor
|
||||
}
|
||||
|
||||
// GetIssueContentHistoryAndPrev get a history and the previous non-deleted history (to compare)
|
||||
func GetIssueContentHistoryAndPrev(dbCtx context.Context, id int64) (history, prevHistory *ContentHistory, err error) {
|
||||
func GetIssueContentHistoryAndPrev(dbCtx context.Context, issueID, id int64) (history, prevHistory *ContentHistory, err error) {
|
||||
history = &ContentHistory{}
|
||||
has, err := db.GetEngine(dbCtx).ID(id).Get(history)
|
||||
has, err := db.GetEngine(dbCtx).Where("id=? AND issue_id=?", id, issueID).Get(history)
|
||||
if err != nil {
|
||||
log.Error("failed to get issue content history %v. err=%v", id, err)
|
||||
return nil, nil, err
|
||||
|
||||
@@ -58,13 +58,13 @@ func TestContentHistory(t *testing.T) {
|
||||
hasHistory2, _ := issues_model.HasIssueContentHistory(dbCtx, 10, 1)
|
||||
assert.False(t, hasHistory2)
|
||||
|
||||
h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6)
|
||||
h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6)
|
||||
assert.EqualValues(t, 6, h6.ID)
|
||||
assert.EqualValues(t, 5, h6Prev.ID)
|
||||
|
||||
// soft-delete
|
||||
_ = issues_model.SoftDeleteIssueContentHistory(dbCtx, 5)
|
||||
h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6)
|
||||
h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6)
|
||||
assert.EqualValues(t, 6, h6.ID)
|
||||
assert.EqualValues(t, 4, h6Prev.ID)
|
||||
|
||||
|
||||
@@ -349,14 +349,21 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
|
||||
From("team_user").
|
||||
Where(builder.Eq{"team_user.uid": reviewRequestedID})
|
||||
|
||||
// if the review is approved or rejected, it should not be shown in the review requested list
|
||||
maxReview := builder.Select("MAX(r.id)").
|
||||
From("review as r").
|
||||
Where(builder.In("r.type", []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest})).
|
||||
GroupBy("r.issue_id, r.reviewer_id, r.reviewer_team_id")
|
||||
|
||||
subQuery := builder.Select("review.issue_id").
|
||||
From("review").
|
||||
Where(builder.And(
|
||||
builder.In("review.type", []ReviewType{ReviewTypeRequest, ReviewTypeReject, ReviewTypeApprove}),
|
||||
builder.Eq{"review.type": ReviewTypeRequest},
|
||||
builder.Or(
|
||||
builder.Eq{"review.reviewer_id": reviewRequestedID},
|
||||
builder.In("review.reviewer_team_id", existInTeamQuery),
|
||||
),
|
||||
builder.In("review.id", maxReview),
|
||||
))
|
||||
return sess.Where("issue.poster_id <> ?", reviewRequestedID).
|
||||
And(builder.In("issue.id", subQuery))
|
||||
|
||||
@@ -306,6 +306,18 @@ func GetProjectByID(ctx context.Context, id int64) (*Project, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GetProjectForRepoByID returns the projects in a repository
|
||||
func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, error) {
|
||||
p := new(Project)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrProjectNotExist{ID: id}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// UpdateProject updates project properties
|
||||
func UpdateProject(ctx context.Context, p *Project) error {
|
||||
if !IsCardTypeValid(p.CardType) {
|
||||
|
||||
@@ -201,6 +201,21 @@ func GetReleaseByID(ctx context.Context, id int64) (*Release, error) {
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// GetReleaseForRepoByID returns release with given ID.
|
||||
func GetReleaseForRepoByID(ctx context.Context, repoID, id int64) (*Release, error) {
|
||||
rel := new(Release)
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("id=? AND repo_id=?", id, repoID).
|
||||
Get(rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrReleaseNotExist{id, ""}
|
||||
}
|
||||
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// FindReleasesOptions describes the conditions to Find releases
|
||||
type FindReleasesOptions struct {
|
||||
db.ListOptions
|
||||
|
||||
@@ -637,12 +637,12 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
|
||||
userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType),
|
||||
)
|
||||
}
|
||||
cond = cond.Or(
|
||||
// 4. Repositories that we directly own
|
||||
builder.Eq{"`repository`.owner_id": user.ID},
|
||||
// 4. Repositories that we directly own
|
||||
cond = cond.Or(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
|
||||
userOrgPublicRepoCond(user.ID),
|
||||
)
|
||||
cond = cond.Or(userOrgPublicRepoCond(user.ID))
|
||||
}
|
||||
}
|
||||
|
||||
return cond
|
||||
|
||||
+29
-33
@@ -94,11 +94,14 @@ func GetSetting(ctx context.Context, key string) (*Setting, error) {
|
||||
const contextCacheKey = "system_setting"
|
||||
|
||||
// GetSettingWithCache returns the setting value via the key
|
||||
func GetSettingWithCache(ctx context.Context, key string) (string, error) {
|
||||
func GetSettingWithCache(ctx context.Context, key, defaultVal string) (string, error) {
|
||||
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
|
||||
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
|
||||
res, err := GetSetting(ctx, key)
|
||||
if err != nil {
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
return defaultVal, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return res.SettingValue, nil
|
||||
@@ -108,17 +111,21 @@ func GetSettingWithCache(ctx context.Context, key string) (string, error) {
|
||||
|
||||
// GetSettingBool return bool value of setting,
|
||||
// none existing keys and errors are ignored and result in false
|
||||
func GetSettingBool(ctx context.Context, key string) bool {
|
||||
s, _ := GetSetting(ctx, key)
|
||||
if s == nil {
|
||||
return false
|
||||
func GetSettingBool(ctx context.Context, key string, defaultVal bool) (bool, error) {
|
||||
s, err := GetSetting(ctx, key)
|
||||
switch {
|
||||
case err == nil:
|
||||
v, _ := strconv.ParseBool(s.SettingValue)
|
||||
return v, nil
|
||||
case IsErrSettingIsNotExist(err):
|
||||
return defaultVal, nil
|
||||
default:
|
||||
return false, err
|
||||
}
|
||||
v, _ := strconv.ParseBool(s.SettingValue)
|
||||
return v
|
||||
}
|
||||
|
||||
func GetSettingWithCacheBool(ctx context.Context, key string) bool {
|
||||
s, _ := GetSettingWithCache(ctx, key)
|
||||
func GetSettingWithCacheBool(ctx context.Context, key string, defaultVal bool) bool {
|
||||
s, _ := GetSettingWithCache(ctx, key, strconv.FormatBool(defaultVal))
|
||||
v, _ := strconv.ParseBool(s)
|
||||
return v
|
||||
}
|
||||
@@ -259,52 +266,41 @@ var (
|
||||
)
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
var disableGravatar bool
|
||||
disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
disableGravatar = setting_module.GetDefaultDisableGravatar()
|
||||
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
|
||||
} else if err != nil {
|
||||
disableGravatar, err := GetSettingBool(ctx, KeyPictureDisableGravatar, setting_module.GetDefaultDisableGravatar())
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
disableGravatar = disableGravatarSetting.GetValueBool()
|
||||
}
|
||||
|
||||
var enableFederatedAvatar bool
|
||||
enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
|
||||
if IsErrSettingIsNotExist(err) {
|
||||
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
|
||||
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
|
||||
} else if err != nil {
|
||||
enableFederatedAvatar, err := GetSettingBool(ctx, KeyPictureEnableFederatedAvatar, setting_module.GetDefaultEnableFederatedAvatar(disableGravatar))
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
enableFederatedAvatar = disableGravatarSetting.GetValueBool()
|
||||
}
|
||||
|
||||
if setting_module.OfflineMode {
|
||||
disableGravatar = true
|
||||
enableFederatedAvatar = false
|
||||
if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
|
||||
if !disableGravatar {
|
||||
if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||
return fmt.Errorf("failed to set setting %q: %w", KeyPictureDisableGravatar, err)
|
||||
}
|
||||
}
|
||||
if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
|
||||
disableGravatar = true
|
||||
|
||||
if enableFederatedAvatar {
|
||||
if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
|
||||
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
||||
return fmt.Errorf("failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
|
||||
}
|
||||
}
|
||||
enableFederatedAvatar = false
|
||||
}
|
||||
|
||||
if enableFederatedAvatar || !disableGravatar {
|
||||
var err error
|
||||
GravatarSourceURL, err = url.Parse(setting_module.GravatarSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
|
||||
return fmt.Errorf("failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
|
||||
}
|
||||
}
|
||||
|
||||
if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() {
|
||||
if GravatarSourceURL != nil && enableFederatedAvatar {
|
||||
LibravatarService = libravatar.New()
|
||||
if GravatarSourceURL.Scheme == "https" {
|
||||
LibravatarService.SetUseHTTPS(true)
|
||||
|
||||
@@ -67,7 +67,9 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||
useLocalAvatar := false
|
||||
autoGenerateAvatar := false
|
||||
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar,
|
||||
setting.GetDefaultDisableGravatar(),
|
||||
)
|
||||
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
|
||||
+34
-33
@@ -392,39 +392,40 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
|
||||
return db.Insert(ctx, ws)
|
||||
}
|
||||
|
||||
// getWebhook uses argument bean as query condition,
|
||||
// ID must be specified and do not assign unnecessary fields.
|
||||
func getWebhook(bean *Webhook) (*Webhook, error) {
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(bean)
|
||||
// GetWebhookByID returns webhook of repository by given ID.
|
||||
func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
|
||||
bean := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(bean)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: bean.ID}
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return bean, nil
|
||||
}
|
||||
|
||||
// GetWebhookByID returns webhook of repository by given ID.
|
||||
func GetWebhookByID(id int64) (*Webhook, error) {
|
||||
return getWebhook(&Webhook{
|
||||
ID: id,
|
||||
})
|
||||
}
|
||||
|
||||
// GetWebhookByRepoID returns webhook of repository by given ID.
|
||||
func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
|
||||
return getWebhook(&Webhook{
|
||||
ID: id,
|
||||
RepoID: repoID,
|
||||
})
|
||||
webhook := new(Webhook)
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
|
||||
func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) {
|
||||
return getWebhook(&Webhook{
|
||||
ID: id,
|
||||
OwnerID: ownerID,
|
||||
})
|
||||
webhook := new(Webhook)
|
||||
has, err := db.GetEngine(db.DefaultContext).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
|
||||
@@ -482,20 +483,20 @@ func UpdateWebhookLastStatus(w *Webhook) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// deleteWebhook uses argument bean as query condition,
|
||||
// DeleteWebhookByID uses argument bean as query condition,
|
||||
// ID must be specified and do not assign unnecessary fields.
|
||||
func deleteWebhook(bean *Webhook) (err error) {
|
||||
func DeleteWebhookByID(id int64) (err error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if count, err := db.DeleteByBean(ctx, bean); err != nil {
|
||||
if count, err := db.DeleteByID(ctx, id, new(Webhook)); err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return ErrWebhookNotExist{ID: bean.ID}
|
||||
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: bean.ID}); err != nil {
|
||||
return ErrWebhookNotExist{ID: id}
|
||||
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -504,16 +505,16 @@ func deleteWebhook(bean *Webhook) (err error) {
|
||||
|
||||
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
|
||||
func DeleteWebhookByRepoID(repoID, id int64) error {
|
||||
return deleteWebhook(&Webhook{
|
||||
ID: id,
|
||||
RepoID: repoID,
|
||||
})
|
||||
if _, err := GetWebhookByRepoID(repoID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return DeleteWebhookByID(id)
|
||||
}
|
||||
|
||||
// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
|
||||
func DeleteWebhookByOwnerID(ownerID, id int64) error {
|
||||
return deleteWebhook(&Webhook{
|
||||
ID: id,
|
||||
OwnerID: ownerID,
|
||||
})
|
||||
if _, err := GetWebhookByOwnerID(ownerID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return DeleteWebhookByID(id)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -197,32 +196,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckForOTP validates OTP
|
||||
func (ctx *APIContext) CheckForOTP() {
|
||||
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
|
||||
return // Skip 2FA
|
||||
}
|
||||
|
||||
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
|
||||
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(otpHeader)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
ctx.Error(http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// APIContexter returns apicontext as middleware
|
||||
func APIContexter() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
|
||||
@@ -101,7 +101,7 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
|
||||
},
|
||||
// find releases without existing repository
|
||||
genericOrphanCheck("Orphaned Releases without existing repository",
|
||||
"release", "repository", "release.repo_id=repository.id"),
|
||||
"release", "repository", "`release`.repo_id=repository.id"),
|
||||
// find pulls without existing issues
|
||||
genericOrphanCheck("Orphaned PullRequests without existing issue",
|
||||
"pull_request", "issue", "pull_request.issue_id=issue.id"),
|
||||
|
||||
+2
-1
@@ -86,7 +86,8 @@ func (repo *Repository) IsEmpty() (bool, error) {
|
||||
Stdout: &output,
|
||||
Stderr: &errbuf,
|
||||
}); err != nil {
|
||||
if err.Error() == "exit status 1" && errbuf.String() == "" {
|
||||
if (err.Error() == "exit status 1" && strings.TrimSpace(errbuf.String()) == "") || err.Error() == "exit status 129" {
|
||||
// git 2.11 exits with 129 if the repo is empty
|
||||
return true, nil
|
||||
}
|
||||
return true, fmt.Errorf("check empty: %w - %s", err, errbuf.String())
|
||||
|
||||
@@ -7,12 +7,17 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
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:
|
||||
// transport.RoundTrip URL=http://domain.com, Host=domain.com
|
||||
// transport.DialContext addrOrHost=domain.com:80
|
||||
@@ -26,11 +31,18 @@ func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
|
||||
Control: func(network, ipAddr string, c syscall.RawConn) (err error) {
|
||||
var host string
|
||||
if host, _, err = net.SplitHostPort(addrOrHost); err != nil {
|
||||
Control: func(network, ipAddr string, c syscall.RawConn) error {
|
||||
host, port, err := net.SplitHostPort(addrOrHost)
|
||||
if err != nil {
|
||||
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
|
||||
tcpAddr, err := net.ResolveTCPAddr(network, ipAddr)
|
||||
if err != nil {
|
||||
|
||||
@@ -66,7 +66,7 @@ var (
|
||||
// well as the HTML5 spec:
|
||||
// http://spec.commonmark.org/0.28/#email-address
|
||||
// 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
|
||||
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`)
|
||||
|
||||
@@ -264,6 +264,18 @@ func TestRender_email(t *testing.T) {
|
||||
"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>`)
|
||||
|
||||
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(
|
||||
"\"info@gitea.com\"",
|
||||
|
||||
@@ -177,6 +177,9 @@ func (m *mailNotifier) NotifyPullRequestPushCommits(ctx context.Context, doer *u
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyPullReviewDismiss(ctx context.Context, doer *user_model.User, review *issues_model.Review, comment *issues_model.Comment) {
|
||||
if err := comment.Review.LoadReviewer(ctx); err != nil {
|
||||
log.Error("Error in PullReviewDismiss while loading reviewer for issue[%d], review[%d] and reviewer[%d]: %v", review.Issue.ID, comment.Review.ID, comment.Review.ReviewerID, err)
|
||||
}
|
||||
if err := mailer.MailParticipantsComment(ctx, comment, activities_model.ActionPullReviewDismissed, review.Issue, nil); err != nil {
|
||||
log.Error("MailParticipantsComment: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/avatars"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -34,42 +35,36 @@ type PushCommits struct {
|
||||
HeadCommit *PushCommit
|
||||
CompareURL string
|
||||
Len int
|
||||
|
||||
avatars map[string]string
|
||||
emailUsers map[string]*user_model.User
|
||||
}
|
||||
|
||||
// NewPushCommits creates a new PushCommits object.
|
||||
func NewPushCommits() *PushCommits {
|
||||
return &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
emailUsers: make(map[string]*user_model.User),
|
||||
}
|
||||
return &PushCommits{}
|
||||
}
|
||||
|
||||
// toAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
|
||||
func (pc *PushCommits) toAPIPayloadCommit(ctx context.Context, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
|
||||
func (pc *PushCommits) toAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
|
||||
var err error
|
||||
authorUsername := ""
|
||||
author, ok := pc.emailUsers[commit.AuthorEmail]
|
||||
author, ok := emailUsers[commit.AuthorEmail]
|
||||
if !ok {
|
||||
author, err = user_model.GetUserByEmail(ctx, commit.AuthorEmail)
|
||||
if err == nil {
|
||||
authorUsername = author.Name
|
||||
pc.emailUsers[commit.AuthorEmail] = author
|
||||
emailUsers[commit.AuthorEmail] = author
|
||||
}
|
||||
} else {
|
||||
authorUsername = author.Name
|
||||
}
|
||||
|
||||
committerUsername := ""
|
||||
committer, ok := pc.emailUsers[commit.CommitterEmail]
|
||||
committer, ok := emailUsers[commit.CommitterEmail]
|
||||
if !ok {
|
||||
committer, err = user_model.GetUserByEmail(ctx, commit.CommitterEmail)
|
||||
if err == nil {
|
||||
// TODO: check errors other than email not found.
|
||||
committerUsername = committer.Name
|
||||
pc.emailUsers[commit.CommitterEmail] = committer
|
||||
emailUsers[commit.CommitterEmail] = committer
|
||||
}
|
||||
} else {
|
||||
committerUsername = committer.Name
|
||||
@@ -107,11 +102,10 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
|
||||
commits := make([]*api.PayloadCommit, len(pc.Commits))
|
||||
var headCommit *api.PayloadCommit
|
||||
|
||||
if pc.emailUsers == nil {
|
||||
pc.emailUsers = make(map[string]*user_model.User)
|
||||
}
|
||||
emailUsers := make(map[string]*user_model.User)
|
||||
|
||||
for i, commit := range pc.Commits {
|
||||
apiCommit, err := pc.toAPIPayloadCommit(ctx, repoPath, repoLink, commit)
|
||||
apiCommit, err := pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -123,7 +117,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
|
||||
}
|
||||
if pc.HeadCommit != nil && headCommit == nil {
|
||||
var err error
|
||||
headCommit, err = pc.toAPIPayloadCommit(ctx, repoPath, repoLink, pc.HeadCommit)
|
||||
headCommit, err = pc.toAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -134,35 +128,21 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
|
||||
// AvatarLink tries to match user in database with e-mail
|
||||
// in order to show custom avatar, and falls back to general avatar link.
|
||||
func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string {
|
||||
if pc.avatars == nil {
|
||||
pc.avatars = make(map[string]string)
|
||||
}
|
||||
avatar, ok := pc.avatars[email]
|
||||
if ok {
|
||||
return avatar
|
||||
}
|
||||
|
||||
size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor
|
||||
|
||||
u, ok := pc.emailUsers[email]
|
||||
if !ok {
|
||||
var err error
|
||||
u, err = user_model.GetUserByEmail(ctx, email)
|
||||
v, _ := cache.GetWithContextCache(ctx, "push_commits", email, func() (string, error) {
|
||||
u, err := user_model.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
pc.avatars[email] = avatars.GenerateEmailAvatarFastLink(ctx, email, size)
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
log.Error("GetUserByEmail: %v", err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
pc.emailUsers[email] = u
|
||||
return avatars.GenerateEmailAvatarFastLink(ctx, email, size), nil
|
||||
}
|
||||
}
|
||||
if u != nil {
|
||||
pc.avatars[email] = u.AvatarLinkWithSize(ctx, size)
|
||||
}
|
||||
return u.AvatarLinkWithSize(ctx, size), nil
|
||||
})
|
||||
|
||||
return pc.avatars[email]
|
||||
return v
|
||||
}
|
||||
|
||||
// CommitToPushCommit transforms a git.Commit to PushCommit type.
|
||||
@@ -189,7 +169,5 @@ func GitToPushCommits(gitCommits []*git.Commit) *PushCommits {
|
||||
HeadCommit: nil,
|
||||
CompareURL: "",
|
||||
Len: len(commits),
|
||||
avatars: make(map[string]string),
|
||||
emailUsers: make(map[string]*user_model.User),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,11 +103,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
|
||||
assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified)
|
||||
}
|
||||
|
||||
func enableGravatar(t *testing.T) {
|
||||
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
|
||||
assert.NoError(t, err)
|
||||
func initGravatarSource(t *testing.T) {
|
||||
setting.GravatarSource = "https://secure.gravatar.com/avatar"
|
||||
err = system_model.Init(db.DefaultContext)
|
||||
err := system_model.Init(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -134,7 +132,7 @@ func TestPushCommits_AvatarLink(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
enableGravatar(t)
|
||||
initGravatarSource(t)
|
||||
|
||||
assert.Equal(t,
|
||||
"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor),
|
||||
|
||||
@@ -174,9 +174,16 @@ func (s *iniConfigSection) ChildSections() (sections []ConfigSection) {
|
||||
return sections
|
||||
}
|
||||
|
||||
func configProviderLoadOptions() ini.LoadOptions {
|
||||
return ini.LoadOptions{
|
||||
KeyValueDelimiterOnWrite: " = ",
|
||||
IgnoreContinuation: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewConfigProviderFromData this function is mainly for testing purpose
|
||||
func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
|
||||
cfg, err := ini.Load(strings.NewReader(configContent))
|
||||
cfg, err := ini.LoadSources(configProviderLoadOptions(), strings.NewReader(configContent))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,7 +197,7 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
|
||||
// NewConfigProviderFromFile load configuration from file.
|
||||
// NOTE: do not print any log except error.
|
||||
func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) {
|
||||
cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "})
|
||||
cfg := ini.Empty(configProviderLoadOptions())
|
||||
loadedFromEmpty := true
|
||||
|
||||
if file != "" {
|
||||
@@ -339,6 +346,7 @@ func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, erro
|
||||
iniFile, err := ini.LoadSources(ini.LoadOptions{
|
||||
IgnoreInlineComment: true,
|
||||
UnescapeValueCommentSymbols: true,
|
||||
IgnoreContinuation: true,
|
||||
}, source, others...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load locale ini: %w", err)
|
||||
|
||||
@@ -30,6 +30,16 @@ key = 123
|
||||
secSub := cfg.Section("foo.bar.xxx")
|
||||
assert.Equal(t, "123", secSub.Key("key").String())
|
||||
})
|
||||
t.Run("TrailingSlash", func(t *testing.T) {
|
||||
cfg, _ := NewConfigProviderFromData(`
|
||||
[foo]
|
||||
key = E:\
|
||||
xxx = yyy
|
||||
`)
|
||||
sec := cfg.Section("foo")
|
||||
assert.Equal(t, "E:\\", sec.Key("key").String())
|
||||
assert.Equal(t, "yyy", sec.Key("xxx").String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigProviderHelper(t *testing.T) {
|
||||
|
||||
@@ -50,7 +50,7 @@ func loadSessionFrom(rootCfg ConfigProvider) {
|
||||
}
|
||||
SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
|
||||
SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash
|
||||
SessionConfig.Secure = sec.Key("COOKIE_SECURE").MustBool(false)
|
||||
SessionConfig.Secure = sec.Key("COOKIE_SECURE").MustBool(strings.HasPrefix(strings.ToLower(AppURL), "https://"))
|
||||
SessionConfig.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(86400)
|
||||
SessionConfig.Maxlifetime = sec.Key("SESSION_LIFE_TIME").MustInt64(86400)
|
||||
SessionConfig.Domain = sec.Key("DOMAIN").String()
|
||||
|
||||
@@ -16,6 +16,7 @@ type Package struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
// swagger:strfmt date-time
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ type Repository struct {
|
||||
Language string `json:"language"`
|
||||
LanguagesURL string `json:"languages_url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
URL string `json:"url"`
|
||||
Link string `json:"link"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
|
||||
@@ -104,7 +104,7 @@ func NewFuncMap() template.FuncMap {
|
||||
return setting.AssetVersion
|
||||
},
|
||||
"DisableGravatar": func(ctx context.Context) bool {
|
||||
return system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
|
||||
return system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar, setting.GetDefaultDisableGravatar())
|
||||
},
|
||||
"DefaultShowFullName": func() bool {
|
||||
return setting.UI.DefaultShowFullName
|
||||
|
||||
@@ -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'>"+
|
||||
"<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>",
|
||||
description,
|
||||
textColor, scopeColor, scopeText,
|
||||
|
||||
+6
-17
@@ -315,10 +315,6 @@ func reqToken() func(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.IsBasicAuth {
|
||||
ctx.CheckForOTP()
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned {
|
||||
return
|
||||
}
|
||||
@@ -340,7 +336,6 @@ func reqBasicAuth() func(ctx *context.APIContext) {
|
||||
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
|
||||
return
|
||||
}
|
||||
ctx.CheckForOTP()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,12 +682,6 @@ 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 {
|
||||
group := auth.NewGroup(
|
||||
&auth.OAuth2{},
|
||||
@@ -1159,8 +1148,8 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("").Get(repo.ListIssues).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
|
||||
m.Get("/pinned", repo.ListPinnedIssues)
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue)
|
||||
m.Get("/pinned", reqRepoReader(unit.TypeIssues), repo.ListPinnedIssues)
|
||||
m.Group("/comments", func() {
|
||||
m.Get("", repo.ListRepoIssueComments)
|
||||
m.Group("/{id}", func() {
|
||||
@@ -1302,10 +1291,10 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
|
||||
})
|
||||
m.Group("/teams", func() {
|
||||
m.Get("", reqToken(), org.ListTeams)
|
||||
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
|
||||
m.Get("/search", reqToken(), org.SearchTeam)
|
||||
}, reqOrgMembership())
|
||||
m.Get("", org.ListTeams)
|
||||
m.Post("", reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam)
|
||||
m.Get("/search", org.SearchTeam)
|
||||
}, reqToken(), reqOrgMembership())
|
||||
m.Group("/labels", func() {
|
||||
m.Get("", org.ListLabels)
|
||||
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
|
||||
|
||||
@@ -241,7 +241,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
apiTeam, err := convert.ToTeam(ctx, team)
|
||||
apiTeam, err := convert.ToTeam(ctx, team, true)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
|
||||
@@ -451,6 +451,24 @@ func ListIssues(ctx *context.APIContext) {
|
||||
isPull = util.OptionalBoolNone
|
||||
}
|
||||
|
||||
if isPull != util.OptionalBoolNone && !ctx.Repo.CanReadIssuesOrPulls(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
|
||||
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||
if ctx.Written() {
|
||||
@@ -561,6 +579,10 @@ func GetIssue(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@@ -69,6 +71,11 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
issue.Repo = ctx.Repo.Repository
|
||||
|
||||
opts := &issues_model.FindCommentsOptions{
|
||||
@@ -265,12 +272,27 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
||||
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{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
Since: since,
|
||||
Before: before,
|
||||
IsPull: isPull,
|
||||
}
|
||||
|
||||
comments, err := issues_model.FindComments(ctx, opts)
|
||||
@@ -357,6 +379,11 @@ func CreateIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
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")))
|
||||
return
|
||||
@@ -426,6 +453,11 @@ func GetIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Type != issues_model.CommentTypeComment {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
@@ -544,7 +576,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -647,7 +689,17 @@ func deleteIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
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)
|
||||
return
|
||||
} else if comment.Type != issues_model.CommentTypeComment {
|
||||
|
||||
@@ -325,6 +325,10 @@ func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
return nil
|
||||
}
|
||||
|
||||
comment.Issue.Repo = ctx.Repo.Repository
|
||||
|
||||
return comment
|
||||
|
||||
@@ -59,6 +59,12 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
|
||||
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
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) {
|
||||
@@ -184,9 +190,19 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
|
||||
return
|
||||
}
|
||||
|
||||
err = comment.LoadIssue(ctx)
|
||||
if err != nil {
|
||||
if err = comment.LoadIssue(ctx); err != nil {
|
||||
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) {
|
||||
|
||||
@@ -155,6 +155,12 @@ func GetDeployKey(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// this check make it more consistent
|
||||
if key.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err = key.GetContent(); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetContent", err)
|
||||
return
|
||||
|
||||
@@ -49,13 +49,12 @@ func GetRelease(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
id := ctx.ParamsInt64(":id")
|
||||
release, err := repo_model.GetReleaseByID(ctx, id)
|
||||
release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
|
||||
return
|
||||
}
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
|
||||
release.IsTag || release.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
@@ -313,13 +312,12 @@ func EditRelease(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.EditReleaseOption)
|
||||
id := ctx.ParamsInt64(":id")
|
||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
||||
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
|
||||
return
|
||||
}
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
|
||||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
@@ -391,17 +389,16 @@ func DeleteRelease(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/empty"
|
||||
|
||||
id := ctx.ParamsInt64(":id")
|
||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
||||
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
|
||||
return
|
||||
}
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
|
||||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
if err := release_service.DeleteReleaseByID(ctx, id, ctx.Doer, false); err != nil {
|
||||
if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
|
||||
return
|
||||
|
||||
@@ -17,6 +17,23 @@ import (
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
|
||||
release, err := repo_model.GetReleaseByID(ctx, releaseID)
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound()
|
||||
return false
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
return false
|
||||
}
|
||||
if release.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetReleaseAttachment gets a single attachment of the release
|
||||
func GetReleaseAttachment(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoGetReleaseAttachment
|
||||
@@ -52,6 +69,10 @@ func GetReleaseAttachment(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/Attachment"
|
||||
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
attachID := ctx.ParamsInt64(":attachment_id")
|
||||
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
|
||||
if err != nil {
|
||||
@@ -170,13 +191,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if release exists an load release
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
release, err := repo_model.GetReleaseByID(ctx, releaseID)
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -197,7 +212,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
attach, err := attachment.UploadAttachment(file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
|
||||
Name: filename,
|
||||
UploaderID: ctx.Doer.ID,
|
||||
RepoID: release.RepoID,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
ReleaseID: releaseID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -256,6 +271,10 @@ func EditReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if release exists an load release
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
attachID := ctx.ParamsInt64(":attachment_id")
|
||||
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
|
||||
if err != nil {
|
||||
@@ -318,6 +337,10 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if release exists an load release
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
attachID := ctx.ParamsInt64(":attachment_id")
|
||||
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
|
||||
if err != nil {
|
||||
|
||||
@@ -112,7 +112,7 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, release.ID, ctx.Doer, false); err != nil {
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
|
||||
return
|
||||
|
||||
@@ -264,7 +264,7 @@ func DeleteTag(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, tag.ID, ctx.Doer, true); err != nil {
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
|
||||
return
|
||||
|
||||
@@ -337,6 +337,10 @@ func GetOauth2Application(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if app.UID != ctx.Doer.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
app.ClientSecret = ""
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ func GetGPGKey(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
key, err := asymkey_model.GetGPGKeyByID(ctx.ParamsInt64(":id"))
|
||||
key, err := asymkey_model.GetGPGKeyForUserByID(ctx.Doer.ID, ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
||||
ctx.NotFound()
|
||||
|
||||
@@ -62,6 +62,11 @@ func GetHook(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Doer.IsAdmin && hook.OwnerID != ctx.Doer.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
|
||||
+50
-51
@@ -19,81 +19,80 @@ import (
|
||||
"code.gitea.io/gitea/modules/web/routing"
|
||||
)
|
||||
|
||||
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
|
||||
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc {
|
||||
prefix = strings.Trim(prefix, "/")
|
||||
funcInfo := routing.GetFuncInfo(storageHandler, prefix)
|
||||
return func(next http.Handler) http.Handler {
|
||||
if storageSetting.MinioConfig.ServeDirect {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" && req.Method != "HEAD" {
|
||||
next.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||
next.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
|
||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||
rPath = util.PathJoinRelX(rPath)
|
||||
|
||||
u, err := objStore.URL(rPath, path.Base(rPath))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||
http.Error(w, "file not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
|
||||
http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
|
||||
})
|
||||
}
|
||||
|
||||
if storageSetting.MinioConfig.ServeDirect {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" && req.Method != "HEAD" {
|
||||
next.ServeHTTP(w, req)
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||
next.ServeHTTP(w, req)
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
|
||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||
rPath = util.PathJoinRelX(rPath)
|
||||
if rPath == "" || rPath == "." {
|
||||
http.Error(w, "file not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := objStore.Stat(rPath)
|
||||
u, err := objStore.URL(rPath, path.Base(rPath))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||
http.Error(w, "file not found", http.StatusNotFound)
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
|
||||
http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fr, err := objStore.Open(rPath)
|
||||
if err != nil {
|
||||
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer fr.Close()
|
||||
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
|
||||
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
|
||||
})
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" && req.Method != "HEAD" {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
|
||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||
rPath = util.PathJoinRelX(rPath)
|
||||
if rPath == "" || rPath == "." {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := objStore.Stat(rPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||
log.Warn("Unable to find %s %s", prefix, rPath)
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fr, err := objStore.Open(rPath)
|
||||
if err != nil {
|
||||
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
|
||||
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer fr.Close()
|
||||
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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,7 +251,6 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
|
||||
isSameRepo = true
|
||||
ci.HeadUser = ctx.Repo.Owner
|
||||
ci.HeadBranch = headInfos[0]
|
||||
|
||||
} else if len(headInfos) == 2 {
|
||||
headInfosSplit := strings.Split(headInfos[0], "/")
|
||||
if len(headInfosSplit) == 1 {
|
||||
@@ -406,6 +405,9 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
|
||||
return nil
|
||||
}
|
||||
defer ci.HeadGitRepo.Close()
|
||||
} else {
|
||||
ctx.NotFound("ParseCompareInfo", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.Data["HeadRepo"] = ci.HeadRepo
|
||||
@@ -785,7 +787,7 @@ func CompareDiff(ctx *context.Context) {
|
||||
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
ctx.Data["IsDiffCompare"] = true
|
||||
templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
|
||||
_, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates)
|
||||
|
||||
if len(templateErrs) > 0 {
|
||||
ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true)
|
||||
|
||||
+43
-10
@@ -804,10 +804,11 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
|
||||
return labels
|
||||
}
|
||||
|
||||
func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) map[string]error {
|
||||
// Tries to load and set an issue template. The first return value indicates if a template was loaded.
|
||||
func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) (bool, map[string]error) {
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
templateCandidates := make([]string, 0, 1+len(possibleFiles))
|
||||
@@ -870,20 +871,15 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
|
||||
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
|
||||
ctx.Data["Reference"] = template.Ref
|
||||
ctx.Data["RefEndName"] = git.RefName(template.Ref).ShortName()
|
||||
return templateErrs
|
||||
return true, templateErrs
|
||||
}
|
||||
return templateErrs
|
||||
return false, templateErrs
|
||||
}
|
||||
|
||||
// NewIssue render creating issue page
|
||||
func NewIssue(ctx *context.Context) {
|
||||
issueConfig, _ := issue_service.GetTemplateConfigFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
hasTemplates := issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
if !issueConfig.BlankIssuesEnabled && hasTemplates {
|
||||
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if blank issues are disabled, just redirect to the "issues/choose" page with these parameters.
|
||||
ctx.Redirect(fmt.Sprintf("%s/issues/new/choose?%s", ctx.Repo.Repository.Link(), ctx.Req.URL.RawQuery), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("repo.issues.new")
|
||||
ctx.Data["PageIsIssueList"] = true
|
||||
@@ -930,7 +926,8 @@ func NewIssue(ctx *context.Context) {
|
||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
|
||||
|
||||
_, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
if errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates); len(errs) > 0 {
|
||||
templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
|
||||
if len(errs) > 0 {
|
||||
for k, v := range errs {
|
||||
templateErrs[k] = v
|
||||
}
|
||||
@@ -945,6 +942,12 @@ func NewIssue(ctx *context.Context) {
|
||||
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypeIssues)
|
||||
|
||||
if !issueConfig.BlankIssuesEnabled && hasTemplates && !templateLoaded {
|
||||
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if blank issues are disabled, just redirect to the "issues/choose" page with these parameters.
|
||||
ctx.Redirect(fmt.Sprintf("%s/issues/new/choose?%s", ctx.Repo.Repository.Link(), ctx.Req.URL.RawQuery), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssueNew)
|
||||
}
|
||||
|
||||
@@ -2965,6 +2968,11 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||
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)) {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
@@ -3031,6 +3039,11 @@ func DeleteComment(ctx *context.Context) {
|
||||
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)) {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
@@ -3157,6 +3170,11 @@ func ChangeCommentReaction(ctx *context.Context) {
|
||||
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 log.IsTrace() {
|
||||
if ctx.IsSigned {
|
||||
@@ -3300,6 +3318,21 @@ func GetCommentAttachments(ctx *context.Context) {
|
||||
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 !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.NotFound("CanReadIssuesOrPulls", issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if !comment.Type.HasAttachmentSupport() {
|
||||
ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
|
||||
return
|
||||
|
||||
@@ -118,7 +118,7 @@ func GetContentHistoryDetail(ctx *context.Context) {
|
||||
}
|
||||
|
||||
historyID := ctx.FormInt64("history_id")
|
||||
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, historyID)
|
||||
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, issue.ID, historyID)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusNotFound, map[string]any{
|
||||
"message": "Can not find the content history",
|
||||
|
||||
@@ -467,7 +467,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||
project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if project_model.IsErrProjectNotExist(err) {
|
||||
ctx.NotFound("", nil)
|
||||
|
||||
+26
-11
@@ -592,7 +592,31 @@ func DeleteTag(ctx *context.Context) {
|
||||
}
|
||||
|
||||
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
||||
if err := releaseservice.DeleteReleaseByID(ctx, ctx.FormInt64("id"), ctx.Doer, isDelTag); err != nil {
|
||||
redirect := func() {
|
||||
if isDelTag {
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"redirect": ctx.Repo.RepoLink + "/tags",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"redirect": ctx.Repo.RepoLink + "/releases",
|
||||
})
|
||||
}
|
||||
|
||||
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound("GetReleaseForRepoByID", err)
|
||||
} else {
|
||||
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
|
||||
redirect()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
||||
} else {
|
||||
@@ -606,14 +630,5 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if isDelTag {
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"redirect": ctx.Repo.RepoLink + "/tags",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"redirect": ctx.Repo.RepoLink + "/releases",
|
||||
})
|
||||
redirect()
|
||||
}
|
||||
|
||||
@@ -821,6 +821,11 @@ func UsernameSubRoute(ctx *context.Context) {
|
||||
reloadParam := func(suffix string) (success bool) {
|
||||
ctx.SetParams("username", strings.TrimSuffix(username, suffix))
|
||||
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()
|
||||
}
|
||||
switch {
|
||||
|
||||
@@ -422,7 +422,7 @@ func PackageSettingsPost(ctx *context.Context) {
|
||||
|
||||
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
|
||||
// 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}); has {
|
||||
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID, IsInternal: util.OptionalBoolFalse}); has {
|
||||
redirectURL = ctx.Package.Descriptor.PackageWebLink()
|
||||
}
|
||||
|
||||
|
||||
@@ -223,10 +223,10 @@ func Profile(ctx *context.Context) {
|
||||
switch tab {
|
||||
case "followers":
|
||||
ctx.Data["Cards"] = followers
|
||||
total = int(count)
|
||||
total = int(numFollowers)
|
||||
case "following":
|
||||
ctx.Data["Cards"] = following
|
||||
total = int(count)
|
||||
total = int(numFollowing)
|
||||
case "activity":
|
||||
date := ctx.FormString("date")
|
||||
items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{
|
||||
|
||||
+6
-19
@@ -174,6 +174,8 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||
return routes
|
||||
}
|
||||
|
||||
var ignSignInAndCsrf = auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{DisableCSRF: true})
|
||||
|
||||
// registerRoutes register routes
|
||||
func registerRoutes(m *web.Route) {
|
||||
reqSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true})
|
||||
@@ -181,7 +183,6 @@ func registerRoutes(m *web.Route) {
|
||||
// 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})
|
||||
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()
|
||||
|
||||
linkAccountEnabled := func(ctx *context.Context) {
|
||||
@@ -863,9 +864,6 @@ func registerRoutes(m *web.Route) {
|
||||
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
|
||||
}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
|
||||
|
||||
// ***** Release Attachment Download without Signin
|
||||
m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload)
|
||||
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/settings", func() {
|
||||
m.Group("", func() {
|
||||
@@ -1118,8 +1116,9 @@ func registerRoutes(m *web.Route) {
|
||||
m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS)
|
||||
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
|
||||
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
|
||||
repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true))
|
||||
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, reqRepoReleaseReader, repo.GetAttachment)
|
||||
repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true))
|
||||
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
|
||||
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/new", repo.NewRelease)
|
||||
m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
|
||||
@@ -1379,19 +1378,7 @@ func registerRoutes(m *web.Route) {
|
||||
})
|
||||
}, ignSignInAndCsrf, lfsServerEnabled)
|
||||
|
||||
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())
|
||||
gitHTTPRouters(m)
|
||||
})
|
||||
})
|
||||
// ***** END: Repository *****
|
||||
|
||||
@@ -37,12 +37,16 @@ func isContainerPath(req *http.Request) bool {
|
||||
}
|
||||
|
||||
var (
|
||||
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/`)
|
||||
gitRawOrAttachPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`)
|
||||
lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
|
||||
)
|
||||
|
||||
func isGitRawReleaseOrLFSPath(req *http.Request) bool {
|
||||
if gitRawReleasePathRe.MatchString(req.URL.Path) {
|
||||
func isGitRawOrAttachPath(req *http.Request) bool {
|
||||
return gitRawOrAttachPathRe.MatchString(req.URL.Path)
|
||||
}
|
||||
|
||||
func isGitRawOrAttachOrLFSPath(req *http.Request) bool {
|
||||
if isGitRawOrAttachPath(req) {
|
||||
return true
|
||||
}
|
||||
if setting.LFS.StartServer {
|
||||
|
||||
@@ -85,6 +85,10 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
||||
"/owner/repo/releases/download/tag/repo.tar.gz",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"/owner/repo/attachments/6d92a9ee-5d8b-4993-97c9-6181bdaa8955",
|
||||
true,
|
||||
},
|
||||
}
|
||||
lfsTests := []string{
|
||||
"/owner/repo/info/lfs/",
|
||||
@@ -104,11 +108,11 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
|
||||
setting.LFS.StartServer = false
|
||||
if got := isGitRawReleaseOrLFSPath(req); got != tt.want {
|
||||
if got := isGitRawOrAttachOrLFSPath(req); got != tt.want {
|
||||
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
setting.LFS.StartServer = true
|
||||
if got := isGitRawReleaseOrLFSPath(req); got != tt.want {
|
||||
if got := isGitRawOrAttachOrLFSPath(req); got != tt.want {
|
||||
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
@@ -117,11 +121,11 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
||||
t.Run(tt, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("POST", tt, nil)
|
||||
setting.LFS.StartServer = false
|
||||
if got := isGitRawReleaseOrLFSPath(req); got != setting.LFS.StartServer {
|
||||
t.Errorf("isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawReleasePathRe.MatchString(tt))
|
||||
if got := isGitRawOrAttachOrLFSPath(req); got != setting.LFS.StartServer {
|
||||
t.Errorf("isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawOrAttachPathRe.MatchString(tt))
|
||||
}
|
||||
setting.LFS.StartServer = true
|
||||
if got := isGitRawReleaseOrLFSPath(req); got != setting.LFS.StartServer {
|
||||
if got := isGitRawOrAttachOrLFSPath(req); got != setting.LFS.StartServer {
|
||||
t.Errorf("isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
|
||||
}
|
||||
})
|
||||
|
||||
+23
-3
@@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
)
|
||||
|
||||
@@ -43,7 +44,7 @@ func (b *Basic) Name() string {
|
||||
// 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) {
|
||||
// Basic authentication should only fire on API, Download or on Git or LFSPaths
|
||||
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) {
|
||||
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -132,11 +133,30 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() {
|
||||
store.GetData()["SkipLocalTwoFA"] = true
|
||||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
||||
if err := validateTOTP(req, u); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("Basic Authorization: Logged in user %-v", u)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
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(req.Header.Get("X-Gitea-OTP")); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return util.NewInvalidArgumentErrorf("invalid provided OTP")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -216,31 +215,6 @@ func VerifyAuthWithOptionsAPI(options *VerifyOptions) func(ctx *context.APIConte
|
||||
})
|
||||
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 {
|
||||
|
||||
@@ -126,7 +126,9 @@ func (o *OAuth2) userIDFromToken(tokenSHA string, store DataStore) int64 {
|
||||
// If verification is successful returns an existing user object.
|
||||
// Returns nil if verification fails.
|
||||
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) {
|
||||
// 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) &&
|
||||
!isGitRawOrAttachPath(req) {
|
||||
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
|
||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) {
|
||||
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) {
|
||||
if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
|
||||
handleSignIn(w, req, sess, user)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
@@ -16,12 +13,7 @@ func WebAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachm
|
||||
}
|
||||
|
||||
func APIAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
|
||||
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)
|
||||
return attach.DownloadURL()
|
||||
}
|
||||
|
||||
// ToAttachment converts models.Attachment to api.Attachment for API usage
|
||||
|
||||
@@ -35,6 +35,7 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m
|
||||
Name: pd.Package.Name,
|
||||
Version: pd.Version.Version,
|
||||
CreatedAt: pd.Version.CreatedUnix.AsTime(),
|
||||
HTMLURL: pd.FullWebLink(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
Parent: parent,
|
||||
Mirror: repo.IsMirror,
|
||||
HTMLURL: repo.HTMLURL(),
|
||||
URL: repoAPIURL,
|
||||
SSHURL: cloneLink.SSH,
|
||||
CloneURL: cloneLink.HTTPS,
|
||||
OriginalURL: repo.SanitizedOriginalURL(),
|
||||
|
||||
@@ -1312,7 +1312,7 @@ outer:
|
||||
}
|
||||
}
|
||||
|
||||
return diff, err
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
// CommentAsDiff returns c.Patch as *Diff
|
||||
|
||||
@@ -53,6 +53,10 @@ func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_mode
|
||||
oldTitle := issue.Title
|
||||
issue.Title = title
|
||||
|
||||
if oldTitle == title {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -282,6 +282,8 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
|
||||
httpClient := NewMigrationHTTPClient()
|
||||
|
||||
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)
|
||||
dlCount := int(asset.DownloadCount)
|
||||
r.Assets = append(r.Assets, &base.ReleaseAsset{
|
||||
@@ -292,18 +294,18 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele
|
||||
Created: asset.Created,
|
||||
DownloadURL: &asset.DownloadURL,
|
||||
DownloadFunc: func() (io.ReadCloser, error) {
|
||||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, asset.ID)
|
||||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, assetID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !hasBaseURL(asset.DownloadURL, g.baseURL) {
|
||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, asset.DownloadURL)
|
||||
if !hasBaseURL(assetDownloadURL, g.baseURL) {
|
||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, assetDownloadURL)
|
||||
return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil
|
||||
}
|
||||
|
||||
// FIXME: for a private download?
|
||||
req, err := http.NewRequest("GET", asset.DownloadURL, nil)
|
||||
req, err := http.NewRequest("GET", assetDownloadURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -309,6 +309,7 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
|
||||
httpClient := NewMigrationHTTPClient()
|
||||
|
||||
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{
|
||||
ID: int64(asset.ID),
|
||||
Name: asset.Name,
|
||||
@@ -316,13 +317,13 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
|
||||
Size: &zero,
|
||||
DownloadCount: &zero,
|
||||
DownloadFunc: func() (io.ReadCloser, error) {
|
||||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, asset.ID, gitlab.WithContext(g.ctx))
|
||||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(g.ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !hasBaseURL(link.URL, g.baseURL) {
|
||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, link.URL)
|
||||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, link.URL)
|
||||
return io.NopCloser(strings.NewReader(link.URL)), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := pr.Issue.LoadPoster(ctx); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker)
|
||||
issueReference := "#"
|
||||
|
||||
@@ -291,17 +291,7 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
|
||||
}
|
||||
|
||||
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
|
||||
func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, delTag bool) error {
|
||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetReleaseByID: %w", err)
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, rel.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %w", err)
|
||||
}
|
||||
|
||||
func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *repo_model.Release, doer *user_model.User, delTag bool) error {
|
||||
if delTag {
|
||||
protectedTags, err := git_model.GetProtectedTags(ctx, rel.RepoID)
|
||||
if err != nil {
|
||||
@@ -334,19 +324,19 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del
|
||||
}, repository.NewPushCommits())
|
||||
notification.NotifyDeleteRef(ctx, doer, repo, refName)
|
||||
|
||||
if err := repo_model.DeleteReleaseByID(ctx, id); err != nil {
|
||||
if err := repo_model.DeleteReleaseByID(ctx, rel.ID); err != nil {
|
||||
return fmt.Errorf("DeleteReleaseByID: %w", err)
|
||||
}
|
||||
} else {
|
||||
rel.IsTag = true
|
||||
|
||||
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
if err := repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
rel.Repo = repo
|
||||
if err = rel.LoadAttributes(ctx); err != nil {
|
||||
if err := rel.LoadAttributes(ctx); err != nil {
|
||||
return fmt.Errorf("LoadAttributes: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
|
||||
// Deliver deliver hook task
|
||||
func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
|
||||
w, err := webhook_model.GetWebhookByID(t.HookID)
|
||||
w, err := webhook_model.GetWebhookByID(ctx, t.HookID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -239,7 +239,7 @@ var (
|
||||
hostMatchers []glob.Glob
|
||||
)
|
||||
|
||||
func webhookProxy() func(req *http.Request) (*url.URL, error) {
|
||||
func webhookProxy(allowList *hostmatcher.HostMatchList) func(req *http.Request) (*url.URL, error) {
|
||||
if setting.Webhook.ProxyURL == "" {
|
||||
return proxy.Proxy()
|
||||
}
|
||||
@@ -257,6 +257,9 @@ func webhookProxy() func(req *http.Request) (*url.URL, error) {
|
||||
return func(req *http.Request) (*url.URL, error) {
|
||||
for _, v := range hostMatchers {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -278,8 +281,8 @@ func Init() error {
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
|
||||
Proxy: webhookProxy(),
|
||||
DialContext: hostmatcher.NewDialContext("webhook", allowedHostMatcher, nil),
|
||||
Proxy: webhookProxy(allowedHostMatcher),
|
||||
DialContext: hostmatcher.NewDialContextWithProxy("webhook", allowedHostMatcher, nil, setting.Webhook.ProxyURLFixed),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,35 +14,72 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/hostmatcher"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWebhookProxy(t *testing.T) {
|
||||
oldWebhook := setting.Webhook
|
||||
t.Cleanup(func() {
|
||||
setting.Webhook = oldWebhook
|
||||
})
|
||||
|
||||
setting.Webhook.ProxyURL = "http://localhost:8080"
|
||||
setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
|
||||
setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}
|
||||
|
||||
kases := map[string]string{
|
||||
"https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx": "http://localhost:8080",
|
||||
"http://s.discordapp.com/assets/xxxxxx": "http://localhost:8080",
|
||||
"http://github.com/a/b": "",
|
||||
allowedHostMatcher := hostmatcher.ParseHostMatchList("webhook.ALLOWED_HOST_LIST", "discordapp.com,s.discordapp.com")
|
||||
|
||||
tests := []struct {
|
||||
req string
|
||||
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)
|
||||
|
||||
for reqURL, proxyURL := range kases {
|
||||
req, err := http.NewRequest("POST", reqURL, nil)
|
||||
assert.NoError(t, err)
|
||||
u, err := webhookProxy(allowedHostMatcher)(req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := webhookProxy()(req)
|
||||
assert.NoError(t, err)
|
||||
if proxyURL == "" {
|
||||
assert.Nil(t, u)
|
||||
} else {
|
||||
assert.EqualValues(t, proxyURL, u.String())
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
got := ""
|
||||
if u != nil {
|
||||
got = u.String()
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,13 @@ func (d *DingtalkPayload) Repository(p *api.RepositoryPayload) (api.Payloader, e
|
||||
func (d *DingtalkPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
|
||||
text, _ := getReleasePayloadInfo(p, noneLinkFormatter, true)
|
||||
|
||||
return createDingtalkPayload(text, text, "view release", p.Release.URL), 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 {
|
||||
|
||||
@@ -238,7 +238,7 @@ func TestDingTalkPayload(t *testing.T) {
|
||||
assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Text)
|
||||
assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.(*DingtalkPayload).ActionCard.Title)
|
||||
assert.Equal(t, "view release", pl.(*DingtalkPayload).ActionCard.SingleTitle)
|
||||
assert.Equal(t, "http://localhost:3000/api/v1/repos/test/repo/releases/2", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
|
||||
assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,13 @@ func (d *DiscordPayload) Wiki(p *api.WikiPayload) (api.Payloader, error) {
|
||||
func (d *DiscordPayload) Release(p *api.ReleasePayload) (api.Payloader, error) {
|
||||
text, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
|
||||
|
||||
return d.createPayload(p.Sender, text, p.Release.Note, p.Release.URL, 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
|
||||
|
||||
@@ -270,7 +270,7 @@ func TestDiscordPayload(t *testing.T) {
|
||||
assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
|
||||
assert.Equal(t, "[test/repo] Release created: v1.0", pl.(*DiscordPayload).Embeds[0].Title)
|
||||
assert.Equal(t, "Note of first stable release", pl.(*DiscordPayload).Embeds[0].Description)
|
||||
assert.Equal(t, "http://localhost:3000/api/v1/repos/test/repo/releases/2", pl.(*DiscordPayload).Embeds[0].URL)
|
||||
assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.(*DiscordPayload).Embeds[0].URL)
|
||||
assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
|
||||
assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user