Compare commits

..

7 Commits

Author SHA1 Message Date
GiteaBot
f20bf2fe3b [skip ci] Updated translations via Crowdin 2023-03-11 00:15:59 +00:00
Zettat123
3de9e63fd0
Hide target selector if tag exists when creating new release (#23171)
Close #22649.

|status|screenshot|
|-|-|
|empty tag name|<img
src="https://user-images.githubusercontent.com/15528715/221490165-fd3abd2e-6dc5-4562-bece-d1d6a305479e.png"
width="300px"/>|
|new tag|<img
src="https://user-images.githubusercontent.com/15528715/221490450-49b2a48e-b206-49f4-bd79-34b1ea64156f.png"
width="300px"/>|
|existing tag|<img
src="https://user-images.githubusercontent.com/15528715/221490301-4d1879dd-4947-4abc-9b9a-e77be1806981.png"
width="300px"/>|
2023-03-10 10:42:38 -06:00
sillyguodong
5155ec35c5
Parse external request id from request headers, and print it in access log (#22906)
Close: #22890.

---
### Configure in .ini file:
```ini
[log]
REQUEST_ID_HEADERS = X-Request-ID, X-Trace-Id
```

### Params in Request Header
```
X-Trace-ID: trace-id-1q2w3e4r
```

![image](https://user-images.githubusercontent.com/33891828/218665296-8fd19a0f-ada6-4236-8bdb-f99201c703e8.png)



### Log output:

![image](https://user-images.githubusercontent.com/33891828/218665225-cc242a57-4ffc-449a-a1f6-f45ded0ead60.png)
2023-03-10 09:54:32 -06:00
yp05327
cf29ee6dd2
Add missing tabs to org projects page (#22705)
Fixes https://github.com/go-gitea/gitea/issues/22676

Context Data `IsOrganizationMember` and `IsOrganizationOwner` is used to
control the visibility of `people` and `team` tab.

2871ea0809/templates/org/menu.tmpl (L19-L40)

And because of the reuse of user projects page, User Context is changed
to Organization Context. But the value of `IsOrganizationMember` and
`IsOrganizationOwner` are not being given.

I reused func `HandleOrgAssignment` to add them to the ctx, but may have
some unnecessary variables, idk whether it is ok.

I found there is a missing `PageIsViewProjects` at create project page.
2023-03-10 09:18:20 -06:00
KN4CK3R
2173f14708
Add user webhooks (#21563)
Currently we can add webhooks for organizations but not for users. This
PR adds the latter. You can access it from the current users settings.


![grafik](https://user-images.githubusercontent.com/1666336/197391408-15dfdc23-b476-4d0c-82f7-9bc9b065988f.png)
2023-03-10 08:28:32 -06:00
zeripath
dad057b639
Handle OpenID discovery URL errors a little nicer when creating/editing sources (#23397)
When there is an error creating a new openIDConnect authentication
source try to handle the error a little better.

Close #23283

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-03-10 01:14:43 -05:00
John Olheiser
f92e0a4018
Split CI pipelines (#23385)
- This PR attempts to split our various DB tests into separate
pipelines.
- It splits up some of the extra feature-related tests rather than
having most of them in the MySQL test.
- It disables the race detector for some of the pipelines as well, as it
can cause slower runs and is mostly redundant when the pipelines just
swap DBs.
- It builds without SQLite support for any of the non-SQLite pipelines.
- It moves the e2e test to using SQLite rather than PG (partially
because I moved the minio tests to PG and that mucked up the test
config, and partially because it avoids another running service)
- It splits up the `go mod download` task in the Makefile from the tool
installation, as the tools are only needed in the compliance pipeline.
(Arguably even some of the tools aren't needed there, but that could be
a follow-up PR)
- SQLite is now the only arm64 pipeline, moving PG back to amd64 which
can leverage autoscaler

Should resolve #22010 - one thing that wasn't changed here but is
mentioned in that issue, unit tests are needed in the same pipeline as
an integration test in order to form a complete coverage report (at
least as far as I could tell), so for now it remains in a pipeline with
a DB integration test.

Please let me know if I've inadvertently changed something that was how
it was on purpose.

---

I will say sometimes it's hard to pin down the average time, as a
pipeline could be waiting for a runner for X minutes and that brings the
total up by X minutes as well, but overall this does seem to be faster
on average.

---------

Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-03-10 01:13:17 -05:00
57 changed files with 1437 additions and 507 deletions

View File

@ -32,6 +32,7 @@ steps:
pull: always pull: always
commands: commands:
- make deps-backend - make deps-backend
- make deps-tools
volumes: volumes:
- name: deps - name: deps
path: /go path: /go
@ -168,7 +169,112 @@ steps:
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: testing-amd64 name: testing-pgsql
platform:
os: linux
arch: amd64
depends_on:
- compliance
trigger:
event:
- push
- tag
- pull_request
paths:
exclude:
- docs/**
volumes:
- name: deps
temp: {}
services:
- name: pgsql
pull: default
image: postgres:15
environment:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
- name: ldap
image: gitea/test-openldap:latest
pull: always
- name: minio
image: minio/minio:RELEASE.2021-03-12T00-00-47Z
pull: always
commands:
- minio server /data
environment:
MINIO_ACCESS_KEY: 123456
MINIO_SECRET_KEY: 12345678
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git config --global --add safe.directory /drone/src
- git fetch --tags --force
when:
event:
exclude:
- pull_request
- name: deps-backend
image: golang:1.20
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: prepare-test-env
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always
commands:
- ./build/test-env-prepare.sh
- name: build
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- ./build/test-env-check.sh
- make backend
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: test-pgsql
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- timeout -s ABRT 50m make test-pgsql-migration test-pgsql
environment:
GOPROXY: https://goproxy.io
TAGS: bindata gogit
RACE_ENABLED: true
TEST_TAGS: gogit
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on: [build]
volumes:
- name: deps
path: /go
---
kind: pipeline
type: docker
name: testing-mysql
platform: platform:
os: linux os: linux
@ -198,40 +304,12 @@ services:
MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: test MYSQL_DATABASE: test
- name: mysql8
image: mysql:8
pull: always
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea
- name: mssql
image: mcr.microsoft.com/mssql/server:latest
pull: always
environment:
ACCEPT_EULA: Y
MSSQL_PID: Standard
SA_PASSWORD: MwantsaSecurePassword1
- name: ldap
image: gitea/test-openldap:latest
pull: always
- name: elasticsearch - name: elasticsearch
image: elasticsearch:7.5.0 image: elasticsearch:7.5.0
pull: always pull: always
environment: environment:
discovery.type: single-node discovery.type: single-node
- name: minio
image: minio/minio:RELEASE.2021-03-12T00-00-47Z
pull: always
commands:
- minio server /data
environment:
MINIO_ACCESS_KEY: 123456
MINIO_SECRET_KEY: 12345678
- name: smtpimap - name: smtpimap
image: tabascoterrier/docker-imap-devel:latest image: tabascoterrier/docker-imap-devel:latest
pull: always pull: always
@ -257,12 +335,6 @@ steps:
- name: deps - name: deps
path: /go path: /go
- name: tag-pre-condition
image: drone/git
pull: always
commands:
- git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA}
- name: prepare-test-env - name: prepare-test-env
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always pull: always
@ -278,7 +350,7 @@ steps:
environment: environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org GOSUMDB: sum.golang.org
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata
depends_on: [deps-backend, prepare-test-env] depends_on: [deps-backend, prepare-test-env]
volumes: volumes:
- name: deps - name: deps
@ -291,7 +363,7 @@ steps:
- make unit-test-coverage test-check - make unit-test-coverage test-check
environment: environment:
GOPROXY: https://goproxy.io GOPROXY: https://goproxy.io
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata
RACE_ENABLED: true RACE_ENABLED: true
GITHUB_READ_TOKEN: GITHUB_READ_TOKEN:
from_secret: github_read_token from_secret: github_read_token
@ -307,7 +379,7 @@ steps:
- make unit-test-coverage test-check - make unit-test-coverage test-check
environment: environment:
GOPROXY: https://goproxy.io GOPROXY: https://goproxy.io
TAGS: bindata gogit sqlite sqlite_unlock_notify TAGS: bindata gogit
RACE_ENABLED: true RACE_ENABLED: true
GITHUB_READ_TOKEN: GITHUB_READ_TOKEN:
from_secret: github_read_token from_secret: github_read_token
@ -325,7 +397,6 @@ steps:
GOPROXY: https://goproxy.io GOPROXY: https://goproxy.io
TAGS: bindata TAGS: bindata
RACE_ENABLED: true RACE_ENABLED: true
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200" TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
depends_on: [build] depends_on: [build]
@ -333,38 +404,6 @@ steps:
- name: deps - name: deps
path: /go path: /go
- name: test-mysql8
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- timeout -s ABRT 50m make test-mysql8-migration test-mysql8
environment:
GOPROXY: https://goproxy.io
TAGS: bindata
RACE_ENABLED: true
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on: [build]
volumes:
- name: deps
path: /go
- name: test-mssql
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- make test-mssql-migration test-mssql
environment:
GOPROXY: https://goproxy.io
TAGS: bindata
RACE_ENABLED: true
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on: [build]
volumes:
- name: deps
path: /go
- name: generate-coverage - name: generate-coverage
image: golang:1.20 image: golang:1.20
commands: commands:
@ -398,7 +437,186 @@ steps:
--- ---
kind: pipeline kind: pipeline
name: testing-arm64 type: docker
name: testing-mysql8
platform:
os: linux
arch: amd64
depends_on:
- compliance
trigger:
event:
- push
- tag
- pull_request
paths:
exclude:
- docs/**
volumes:
- name: deps
temp: {}
services:
- name: mysql8
image: mysql:8
pull: always
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git config --global --add safe.directory /drone/src
- git fetch --tags --force
when:
event:
exclude:
- pull_request
- name: deps-backend
image: golang:1.20
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: prepare-test-env
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always
commands:
- ./build/test-env-prepare.sh
- name: build
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- ./build/test-env-check.sh
- make backend
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: test-mysql8
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- timeout -s ABRT 50m make test-mysql8-migration test-mysql8
environment:
GOPROXY: https://goproxy.io
TAGS: bindata
USE_REPO_TEST_DIR: 1
depends_on: [build]
volumes:
- name: deps
path: /go
---
kind: pipeline
type: docker
name: testing-mssql
platform:
os: linux
arch: amd64
depends_on:
- compliance
trigger:
event:
- push
- tag
- pull_request
paths:
exclude:
- docs/**
volumes:
- name: deps
temp: {}
services:
- name: mssql
image: mcr.microsoft.com/mssql/server:latest
pull: always
environment:
ACCEPT_EULA: Y
MSSQL_PID: Standard
SA_PASSWORD: MwantsaSecurePassword1
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git config --global --add safe.directory /drone/src
- git fetch --tags --force
when:
event:
exclude:
- pull_request
- name: deps-backend
image: golang:1.20
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: prepare-test-env
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always
commands:
- ./build/test-env-prepare.sh
- name: build
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- ./build/test-env-check.sh
- make backend
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
GOSUMDB: sum.golang.org
TAGS: bindata
depends_on: [deps-backend, prepare-test-env]
volumes:
- name: deps
path: /go
- name: test-mssql
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- make test-mssql-migration test-mssql
environment:
GOPROXY: https://goproxy.io
TAGS: bindata
USE_REPO_TEST_DIR: 1
depends_on: [build]
volumes:
- name: deps
path: /go
---
kind: pipeline
name: testing-sqlite
platform: platform:
os: linux os: linux
@ -420,18 +638,6 @@ volumes:
- name: deps - name: deps
temp: {} temp: {}
services:
- name: pgsql
pull: default
image: postgres:10
environment:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
- name: ldap
pull: default
image: gitea/test-openldap:latest
steps: steps:
- name: fetch-tags - name: fetch-tags
image: docker:git image: docker:git
@ -490,23 +696,6 @@ steps:
- name: deps - name: deps
path: /go path: /go
- name: test-pgsql
image: gitea/test_env:linux-arm64 # https://gitea.com/gitea/test-env
user: gitea
commands:
- timeout -s ABRT 50m make test-pgsql-migration test-pgsql
environment:
GOPROXY: https://goproxy.io
TAGS: bindata gogit
RACE_ENABLED: true
TEST_TAGS: gogit
TEST_LDAP: 1
USE_REPO_TEST_DIR: 1
depends_on: [build]
volumes:
- name: deps
path: /go
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
@ -530,15 +719,6 @@ volumes:
- name: deps - name: deps
temp: {} temp: {}
services:
- name: pgsql
pull: default
image: postgres:10
environment:
POSTGRES_DB: testgitea-e2e
POSTGRES_PASSWORD: postgres
POSTGRES_INITDB_ARGS: --encoding=UTF8 --lc-collate='en_US.UTF-8' --lc-ctype='en_US.UTF-8'
steps: steps:
- name: deps-frontend - name: deps-frontend
image: node:18 image: node:18
@ -568,14 +748,12 @@ steps:
- curl -sLO https://go.dev/dl/go1.20.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.20.linux-amd64.tar.gz - curl -sLO https://go.dev/dl/go1.20.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.20.linux-amd64.tar.gz
- groupadd --gid 1001 gitea && useradd -m --gid 1001 --uid 1001 gitea - groupadd --gid 1001 gitea && useradd -m --gid 1001 --uid 1001 gitea
- apt-get -qq update && apt-get -qqy install build-essential - apt-get -qq update && apt-get -qqy install build-essential
- export TEST_PGSQL_SCHEMA=''
- ./build/test-env-prepare.sh - ./build/test-env-prepare.sh
- su gitea bash -c "export PATH=$PATH:/usr/local/go/bin && timeout -s ABRT 40m make test-e2e-pgsql" - su gitea bash -c "export PATH=$PATH:/usr/local/go/bin && timeout -s ABRT 40m make test-e2e-sqlite"
environment: environment:
GOPROXY: https://goproxy.io GOPROXY: https://goproxy.io
GOSUMDB: sum.golang.org GOSUMDB: sum.golang.org
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
TEST_PGSQL_DBNAME: 'testgitea-e2e'
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
depends_on: [build-frontend, deps-backend] depends_on: [build-frontend, deps-backend]
volumes: volumes:
@ -709,8 +887,11 @@ trigger:
- docs/** - docs/**
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
volumes: volumes:
- name: deps - name: deps
@ -842,8 +1023,11 @@ trigger:
- tag - tag
depends_on: depends_on:
- testing-arm64 - testing-mysql
- testing-amd64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
volumes: volumes:
- name: deps - name: deps
@ -994,8 +1178,11 @@ platform:
arch: amd64 arch: amd64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1064,8 +1251,11 @@ platform:
arch: amd64 arch: amd64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1129,8 +1319,11 @@ platform:
arch: amd64 arch: amd64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1192,8 +1385,11 @@ platform:
arch: amd64 arch: amd64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1292,8 +1488,11 @@ platform:
arch: arm64 arch: arm64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1362,8 +1561,11 @@ platform:
arch: arm64 arch: arm64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1427,8 +1629,11 @@ platform:
arch: arm64 arch: arm64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1493,8 +1698,11 @@ platform:
arch: arm64 arch: arm64
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
trigger: trigger:
ref: ref:
@ -1607,7 +1815,6 @@ platform:
steps: steps:
- name: manifest-rootless - name: manifest-rootless
pull: always
image: plugins/manifest image: plugins/manifest
pull: always pull: always
settings: settings:
@ -1671,8 +1878,11 @@ trigger:
- failure - failure
depends_on: depends_on:
- testing-amd64 - testing-mysql
- testing-arm64 - testing-mysql8
- testing-mssql
- testing-pgsql
- testing-sqlite
- release-version - release-version
- release-latest - release-latest
- docker-linux-amd64-release - docker-linux-amd64-release

View File

@ -190,6 +190,7 @@ help:
@echo " - deps install dependencies" @echo " - deps install dependencies"
@echo " - deps-frontend install frontend dependencies" @echo " - deps-frontend install frontend dependencies"
@echo " - deps-backend install backend dependencies" @echo " - deps-backend install backend dependencies"
@echo " - deps-tools install tool dependencies"
@echo " - lint lint everything" @echo " - lint lint everything"
@echo " - lint-frontend lint frontend files" @echo " - lint-frontend lint frontend files"
@echo " - lint-backend lint backend files" @echo " - lint-backend lint backend files"
@ -821,7 +822,7 @@ docs:
cd docs; make trans-copy clean build-offline; cd docs; make trans-copy clean build-offline;
.PHONY: deps .PHONY: deps
deps: deps-frontend deps-backend deps: deps-frontend deps-backend deps-tools
.PHONY: deps-frontend .PHONY: deps-frontend
deps-frontend: node_modules deps-frontend: node_modules
@ -829,6 +830,9 @@ deps-frontend: node_modules
.PHONY: deps-backend .PHONY: deps-backend
deps-backend: deps-backend:
$(GO) mod download $(GO) mod download
.PHONY: deps-tools
deps-tools:
$(GO) install $(AIR_PACKAGE) $(GO) install $(AIR_PACKAGE)
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) $(GO) install $(EDITORCONFIG_CHECKER_PACKAGE)
$(GO) install $(ERRCHECK_PACKAGE) $(GO) install $(ERRCHECK_PACKAGE)

View File

@ -7,6 +7,7 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/url"
"os" "os"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
@ -469,11 +470,19 @@ func runAddOauth(c *cli.Context) error {
return err return err
} }
config := parseOAuth2Config(c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
}
}
return auth_model.CreateSource(&auth_model.Source{ return auth_model.CreateSource(&auth_model.Source{
Type: auth_model.OAuth2, Type: auth_model.OAuth2,
Name: c.String("name"), Name: c.String("name"),
IsActive: true, IsActive: true,
Cfg: parseOAuth2Config(c), Cfg: config,
}) })
} }

View File

@ -576,6 +576,22 @@ ROUTER = console
;; The routing level will default to that of the system but individual router level can be set in ;; The routing level will default to that of the system but individual router level can be set in
;; [log.<mode>.router] LEVEL ;; [log.<mode>.router] LEVEL
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Print request id which parsed from request headers in access log, when access log is enabled.
;; * E.g:
;; * In request Header: X-Request-ID: test-id-123
;; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID
;; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "test-id-123"
;;
;; If you configure more than one in the .ini file, it will match in the order of configuration,
;; and the first match will be finally printed in the log.
;; * E.g:
;; * In reuqest Header: X-Trace-ID: trace-id-1q2w3e4r
;; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID, X-Trace-ID, X-Req-ID
;; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "trace-id-1q2w3e4r"
;;
;; REQUEST_ID_HEADERS =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;

View File

@ -881,7 +881,13 @@ Default templates for project boards:
- `Identity`: the SignedUserName or `"-"` if not logged in. - `Identity`: the SignedUserName or `"-"` if not logged in.
- `Start`: the start time of the request. - `Start`: the start time of the request.
- `ResponseWriter`: the responseWriter from the request. - `ResponseWriter`: the responseWriter from the request.
- `RequestID`: the value matching REQUEST_ID_HEADERSdefault: `-`, if not matched.
- You must be very careful to ensure that this template does not throw errors or panics as this template runs outside of the panic/recovery script. - You must be very careful to ensure that this template does not throw errors or panics as this template runs outside of the panic/recovery script.
- `REQUEST_ID_HEADERS`: **\<empty\>**: You can configure multiple values that are splited by comma here. It will match in the order of configuration, and the first match will be finally printed in the access log.
- e.g.
- In the Request Header: X-Request-ID: **test-id-123**
- Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID
- Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "**test-id-123**" ...
### Log subsections (`log.name`, `log.name.*`) ### Log subsections (`log.name`, `log.name.*`)

View File

@ -262,7 +262,22 @@ test01.xls: application/vnd.ms-excel; charset=binary
- `ROOT_PATH`: 日志文件根目录。 - `ROOT_PATH`: 日志文件根目录。
- `MODE`: 日志记录模式,默认是为 `console`。如果要写到多个通道,用逗号分隔 - `MODE`: 日志记录模式,默认是为 `console`。如果要写到多个通道,用逗号分隔
- `LEVEL`: 日志级别,默认为`Trace` - `LEVEL`: 日志级别,默认为 `Trace`
- `DISABLE_ROUTER_LOG`: 关闭日志中的路由日志。
- `ENABLE_ACCESS_LOG`: 是否开启 Access Log, 默认为 false。
- `ACCESS_LOG_TEMPLATE`: `access.log` 输出内容的模板,默认模板:**`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`**
模板支持以下参数:
- `Ctx`: 请求上下文。
- `Identity`: 登录用户名,默认: “`-`”。
- `Start`: 请求开始时间。
- `ResponseWriter`:
- `RequestID`: 从请求头中解析得到的与 `REQUEST_ID_HEADERS` 匹配的值,默认: “`-`”。
- 一定要谨慎配置该模板否则可能会引起panic.
- `REQUEST_ID_HEADERS`: 从 Request Header 中匹配指定 Key并将匹配到的值输出到 `access.log` 中(需要在 `ACCESS_LOG_TEMPLATE` 中指定输出位置)。如果在该参数中配置多个 Key 请用逗号分割,程序将按照配置的顺序进行匹配。
- 示例:
- 请求头: X-Request-ID: **test-id-123**
- 配置文件: REQUEST_ID_HEADERS = X-Request-ID
- 日志输出: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "**test-id-123**" ...
## Cron (`cron`) ## Cron (`cron`)

View File

@ -60,6 +60,7 @@ Gitea supports the following scopes for tokens:
| &nbsp;&nbsp;&nbsp; **write:public_key** | Grant read/write access to public keys | | &nbsp;&nbsp;&nbsp; **write:public_key** | Grant read/write access to public keys |
| &nbsp;&nbsp;&nbsp; **read:public_key** | Grant read-only access to public keys | | &nbsp;&nbsp;&nbsp; **read:public_key** | Grant read-only access to public keys |
| **admin:org_hook** | Grants full access to organizational-level hooks | | **admin:org_hook** | Grants full access to organizational-level hooks |
| **admin:user_hook** | Grants full access to user-level hooks |
| **notification** | Grants full access to notifications | | **notification** | Grants full access to notifications |
| **user** | Grants full access to user profile info | | **user** | Grants full access to user profile info |
| &nbsp;&nbsp;&nbsp; **read:user** | Grants read access to user's profile | | &nbsp;&nbsp;&nbsp; **read:user** | Grants read access to user's profile |

View File

@ -32,6 +32,8 @@ const (
AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook" AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook"
AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook"
AccessTokenScopeNotification AccessTokenScope = "notification" AccessTokenScopeNotification AccessTokenScope = "notification"
AccessTokenScopeUser AccessTokenScope = "user" AccessTokenScopeUser AccessTokenScope = "user"
@ -64,7 +66,7 @@ type AccessTokenScopeBitmap uint64
const ( const (
// AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`. // AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`.
AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits | AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits |
AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits | AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits | AccessTokenScopeAdminUserHookBits |
AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits | AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits |
AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits
@ -86,6 +88,8 @@ const (
AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota
AccessTokenScopeAdminUserHookBits AccessTokenScopeBitmap = 1 << iota
AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota
AccessTokenScopeUserBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits AccessTokenScopeUserBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits
@ -123,6 +127,7 @@ var allAccessTokenScopes = []AccessTokenScope{
AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey, AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey,
AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook, AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook,
AccessTokenScopeAdminOrgHook, AccessTokenScopeAdminOrgHook,
AccessTokenScopeAdminUserHook,
AccessTokenScopeNotification, AccessTokenScopeNotification,
AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow, AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow,
AccessTokenScopeDeleteRepo, AccessTokenScopeDeleteRepo,
@ -147,6 +152,7 @@ var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{
AccessTokenScopeWriteRepoHook: AccessTokenScopeWriteRepoHookBits, AccessTokenScopeWriteRepoHook: AccessTokenScopeWriteRepoHookBits,
AccessTokenScopeReadRepoHook: AccessTokenScopeReadRepoHookBits, AccessTokenScopeReadRepoHook: AccessTokenScopeReadRepoHookBits,
AccessTokenScopeAdminOrgHook: AccessTokenScopeAdminOrgHookBits, AccessTokenScopeAdminOrgHook: AccessTokenScopeAdminOrgHookBits,
AccessTokenScopeAdminUserHook: AccessTokenScopeAdminUserHookBits,
AccessTokenScopeNotification: AccessTokenScopeNotificationBits, AccessTokenScopeNotification: AccessTokenScopeNotificationBits,
AccessTokenScopeUser: AccessTokenScopeUserBits, AccessTokenScopeUser: AccessTokenScopeUserBits,
AccessTokenScopeReadUser: AccessTokenScopeReadUserBits, AccessTokenScopeReadUser: AccessTokenScopeReadUserBits,
@ -263,7 +269,7 @@ func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope {
scope := AccessTokenScope(strings.Join(scopes, ",")) scope := AccessTokenScope(strings.Join(scopes, ","))
scope = AccessTokenScope(strings.ReplaceAll( scope = AccessTokenScope(strings.ReplaceAll(
string(scope), string(scope),
"repo,admin:org,admin:public_key,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "repo,admin:org,admin:public_key,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application",
"all", "all",
)) ))
return scope return scope

View File

@ -40,8 +40,8 @@ func TestAccessTokenScope_Normalize(t *testing.T) {
{"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil}, {"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil},
{"admin:application,write:application,user", "user,admin:application", nil}, {"admin:application,write:application,user", "user,admin:application", nil},
{"all", "all", nil}, {"all", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil}, {"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil}, {"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -16,7 +16,7 @@
- -
id: 3 id: 3
org_id: 3 owner_id: 3
repo_id: 3 repo_id: 3
url: www.example.com/url3 url: www.example.com/url3
content_type: 1 # json content_type: 1 # json

View File

@ -467,6 +467,8 @@ var migrations = []Migration{
// v244 -> v245 // v244 -> v245
NewMigration("Add NeedApproval to actions tables", v1_20.AddNeedApprovalToActionRun), NewMigration("Add NeedApproval to actions tables", v1_20.AddNeedApprovalToActionRun),
// v245 -> v246
NewMigration("Rename Webhook org_id to owner_id", v1_20.RenameWebhookOrgToOwner),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,74 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_20 //nolint
import (
"context"
"fmt"
"code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm"
)
func RenameWebhookOrgToOwner(x *xorm.Engine) error {
type Webhook struct {
OrgID int64 `xorm:"INDEX"`
}
// This migration maybe rerun so that we should check if it has been run
ownerExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "owner_id")
if err != nil {
return err
}
if ownerExist {
orgExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webhook", "org_id")
if err != nil {
return err
}
if !orgExist {
return nil
}
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := sess.Sync2(new(Webhook)); err != nil {
return err
}
if ownerExist {
if err := base.DropTableColumns(sess, "webhook", "owner_id"); err != nil {
return err
}
}
switch {
case setting.Database.Type.IsMySQL():
inferredTable, err := x.TableInfo(new(Webhook))
if err != nil {
return err
}
sqlType := x.Dialect().SQLType(inferredTable.GetColumn("org_id"))
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `webhook` CHANGE org_id owner_id %s", sqlType)); err != nil {
return err
}
case setting.Database.Type.IsMSSQL():
if _, err := sess.Exec("sp_rename 'webhook.org_id', 'owner_id', 'COLUMN'"); err != nil {
return err
}
default:
if _, err := sess.Exec("ALTER TABLE `webhook` RENAME COLUMN org_id TO owner_id"); err != nil {
return err
}
}
return sess.Commit()
}

View File

@ -239,6 +239,32 @@ func (org *Organization) CustomAvatarRelativePath() string {
return org.Avatar return org.Avatar
} }
// UnitPermission returns unit permission
func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.User, unitType unit.Type) perm.AccessMode {
if doer != nil {
teams, err := GetUserOrgTeams(ctx, org.ID, doer.ID)
if err != nil {
log.Error("GetUserOrgTeams: %v", err)
return perm.AccessModeNone
}
if err := teams.LoadUnits(ctx); err != nil {
log.Error("LoadUnits: %v", err)
return perm.AccessModeNone
}
if len(teams) > 0 {
return teams.UnitMaxAccess(unitType)
}
}
if org.Visibility.IsPublic() {
return perm.AccessModeRead
}
return perm.AccessModeNone
}
// CreateOrganization creates record of a new organization. // CreateOrganization creates record of a new organization.
func CreateOrganization(org *Organization, owner *user_model.User) (err error) { func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
if !owner.CanCreateOrganization() { if !owner.CanCreateOrganization() {

View File

@ -393,6 +393,11 @@ func (u *User) IsOrganization() bool {
return u.Type == UserTypeOrganization return u.Type == UserTypeOrganization
} }
// IsIndividual returns true if user is actually a individual user.
func (u *User) IsIndividual() bool {
return u.Type == UserTypeIndividual
}
// DisplayName returns full name if it's not empty, // DisplayName returns full name if it's not empty,
// returns username otherwise. // returns username otherwise.
func (u *User) DisplayName() string { func (u *User) DisplayName() string {

View File

@ -122,7 +122,7 @@ func IsValidHookContentType(name string) bool {
type Webhook struct { type Webhook struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
OrgID int64 `xorm:"INDEX"` OwnerID int64 `xorm:"INDEX"`
IsSystemWebhook bool IsSystemWebhook bool
URL string `xorm:"url TEXT"` URL string `xorm:"url TEXT"`
HTTPMethod string `xorm:"http_method"` HTTPMethod string `xorm:"http_method"`
@ -412,11 +412,11 @@ func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
}) })
} }
// GetWebhookByOrgID returns webhook of organization by given ID. // GetWebhookByOwnerID returns webhook of a user or organization by given ID.
func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) { func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) {
return getWebhook(&Webhook{ return getWebhook(&Webhook{
ID: id, ID: id,
OrgID: orgID, OwnerID: ownerID,
}) })
} }
@ -424,7 +424,7 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
type ListWebhookOptions struct { type ListWebhookOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
OrgID int64 OwnerID int64
IsActive util.OptionalBool IsActive util.OptionalBool
} }
@ -433,8 +433,8 @@ func (opts *ListWebhookOptions) toCond() builder.Cond {
if opts.RepoID != 0 { if opts.RepoID != 0 {
cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
} }
if opts.OrgID != 0 { if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"webhook.org_id": opts.OrgID}) cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
} }
if !opts.IsActive.IsNone() { if !opts.IsActive.IsNone() {
cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()}) cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()})
@ -503,10 +503,10 @@ func DeleteWebhookByRepoID(repoID, id int64) error {
}) })
} }
// DeleteWebhookByOrgID deletes webhook of organization by given ID. // DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
func DeleteWebhookByOrgID(orgID, id int64) error { func DeleteWebhookByOwnerID(ownerID, id int64) error {
return deleteWebhook(&Webhook{ return deleteWebhook(&Webhook{
ID: id, ID: id,
OrgID: orgID, OwnerID: ownerID,
}) })
} }

View File

@ -15,7 +15,7 @@ import (
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)
return webhooks, db.GetEngine(ctx). return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, false).
Find(&webhooks) Find(&webhooks)
} }
@ -23,7 +23,7 @@ func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) { func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) {
webhook := &Webhook{ID: id} webhook := &Webhook{ID: id}
has, err := db.GetEngine(ctx). has, err := db.GetEngine(ctx).
Where("repo_id=? AND org_id=?", 0, 0). Where("repo_id=? AND owner_id=?", 0, 0).
Get(webhook) Get(webhook)
if err != nil { if err != nil {
return nil, err return nil, err
@ -38,11 +38,11 @@ func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webh
webhooks := make([]*Webhook, 0, 5) webhooks := make([]*Webhook, 0, 5)
if isActive.IsNone() { if isActive.IsNone() {
return webhooks, db.GetEngine(ctx). return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true).
Find(&webhooks) Find(&webhooks)
} }
return webhooks, db.GetEngine(ctx). return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()).
Find(&webhooks) Find(&webhooks)
} }
@ -50,7 +50,7 @@ func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webh
func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error { func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error {
return db.WithTx(ctx, func(ctx context.Context) error { return db.WithTx(ctx, func(ctx context.Context) error {
count, err := db.GetEngine(ctx). count, err := db.GetEngine(ctx).
Where("repo_id=? AND org_id=?", 0, 0). Where("repo_id=? AND owner_id=?", 0, 0).
Delete(&Webhook{ID: id}) Delete(&Webhook{ID: id})
if err != nil { if err != nil {
return err return err

View File

@ -109,13 +109,13 @@ func TestGetWebhookByRepoID(t *testing.T) {
assert.True(t, IsErrWebhookNotExist(err)) assert.True(t, IsErrWebhookNotExist(err))
} }
func TestGetWebhookByOrgID(t *testing.T) { func TestGetWebhookByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hook, err := GetWebhookByOrgID(3, 3) hook, err := GetWebhookByOwnerID(3, 3)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int64(3), hook.ID) assert.Equal(t, int64(3), hook.ID)
_, err = GetWebhookByOrgID(unittest.NonexistentID, unittest.NonexistentID) _, err = GetWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrWebhookNotExist(err)) assert.True(t, IsErrWebhookNotExist(err))
} }
@ -140,9 +140,9 @@ func TestGetWebhooksByRepoID(t *testing.T) {
} }
} }
func TestGetActiveWebhooksByOrgID(t *testing.T) { func TestGetActiveWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OrgID: 3, IsActive: util.OptionalBoolTrue}) hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OwnerID: 3, IsActive: util.OptionalBoolTrue})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID) assert.Equal(t, int64(3), hooks[0].ID)
@ -150,9 +150,9 @@ func TestGetActiveWebhooksByOrgID(t *testing.T) {
} }
} }
func TestGetWebhooksByOrgID(t *testing.T) { func TestGetWebhooksByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OrgID: 3}) hooks, err := ListWebhooksByOpts(db.DefaultContext, &ListWebhookOptions{OwnerID: 3})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, hooks, 1) { if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(3), hooks[0].ID) assert.Equal(t, int64(3), hooks[0].ID)
@ -181,13 +181,13 @@ func TestDeleteWebhookByRepoID(t *testing.T) {
assert.True(t, IsErrWebhookNotExist(err)) assert.True(t, IsErrWebhookNotExist(err))
} }
func TestDeleteWebhookByOrgID(t *testing.T) { func TestDeleteWebhookByOwnerID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OrgID: 3}) unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3})
assert.NoError(t, DeleteWebhookByOrgID(3, 3)) assert.NoError(t, DeleteWebhookByOwnerID(3, 3))
unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OrgID: 3}) unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3})
err := DeleteWebhookByOrgID(unittest.NonexistentID, unittest.NonexistentID) err := DeleteWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrWebhookNotExist(err)) assert.True(t, IsErrWebhookNotExist(err))
} }

View File

@ -6,7 +6,9 @@ package context
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"net/http" "net/http"
"strings"
"text/template" "text/template"
"time" "time"
@ -20,13 +22,39 @@ type routerLoggerOptions struct {
Start *time.Time Start *time.Time
ResponseWriter http.ResponseWriter ResponseWriter http.ResponseWriter
Ctx map[string]interface{} Ctx map[string]interface{}
RequestID *string
} }
var signedUserNameStringPointerKey interface{} = "signedUserNameStringPointerKey" var signedUserNameStringPointerKey interface{} = "signedUserNameStringPointerKey"
const keyOfRequestIDInTemplate = ".RequestID"
// According to:
// TraceId: A valid trace identifier is a 16-byte array with at least one non-zero byte
// MD5 output is 16 or 32 bytes: md5-bytes is 16, md5-hex is 32
// SHA1: similar, SHA1-bytes is 20, SHA1-hex is 40.
// UUID is 128-bit, 32 hex chars, 36 ASCII chars with 4 dashes
// So, we accept a Request ID with a maximum character length of 40
const maxRequestIDByteLength = 40
func parseRequestIDFromRequestHeader(req *http.Request) string {
requestID := "-"
for _, key := range setting.Log.RequestIDHeaders {
if req.Header.Get(key) != "" {
requestID = req.Header.Get(key)
break
}
}
if len(requestID) > maxRequestIDByteLength {
requestID = fmt.Sprintf("%s...", requestID[:maxRequestIDByteLength])
}
return requestID
}
// AccessLogger returns a middleware to log access logger // AccessLogger returns a middleware to log access logger
func AccessLogger() func(http.Handler) http.Handler { func AccessLogger() func(http.Handler) http.Handler {
logger := log.GetLogger("access") logger := log.GetLogger("access")
needRequestID := len(setting.Log.RequestIDHeaders) > 0 && strings.Contains(setting.Log.AccessLogTemplate, keyOfRequestIDInTemplate)
logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate) logTemplate, _ := template.New("log").Parse(setting.Log.AccessLogTemplate)
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@ -34,6 +62,11 @@ func AccessLogger() func(http.Handler) http.Handler {
identity := "-" identity := "-"
r := req.WithContext(context.WithValue(req.Context(), signedUserNameStringPointerKey, &identity)) r := req.WithContext(context.WithValue(req.Context(), signedUserNameStringPointerKey, &identity))
var requestID string
if needRequestID {
requestID = parseRequestIDFromRequestHeader(req)
}
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
rw := w.(ResponseWriter) rw := w.(ResponseWriter)
@ -47,6 +80,7 @@ func AccessLogger() func(http.Handler) http.Handler {
"RemoteAddr": req.RemoteAddr, "RemoteAddr": req.RemoteAddr,
"Req": req, "Req": req,
}, },
RequestID: &requestID,
}) })
if err != nil { if err != nil {
log.Error("Could not set up chi access logger: %v", err.Error()) log.Error("Could not set up chi access logger: %v", err.Error())

View File

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
) )
@ -31,29 +30,34 @@ type Organization struct {
} }
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool { func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
if ctx.Doer == nil { return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeWrite
return false
}
return org.UnitPermission(ctx, ctx.Doer.ID, unitType) >= perm.AccessModeWrite
} }
func (org *Organization) UnitPermission(ctx *Context, doerID int64, unitType unit.Type) perm.AccessMode { func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
if doerID > 0 { return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead
teams, err := organization.GetUserOrgTeams(ctx, org.Organization.ID, doerID) }
if err != nil {
log.Error("GetUserOrgTeams: %v", err)
return perm.AccessModeNone
}
if len(teams) > 0 {
return teams.UnitMaxAccess(unitType)
}
}
if org.Organization.Visibility == structs.VisibleTypePublic { func GetOrganizationByParams(ctx *Context) {
return perm.AccessModeRead orgName := ctx.Params(":org")
}
return perm.AccessModeNone var err error
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil {
if organization.IsErrOrgNotExist(err) {
redirectUserID, err := user_model.LookupUserRedirect(orgName)
if err == nil {
RedirectToUser(ctx, orgName, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
ctx.NotFound("GetUserByName", err)
} else {
ctx.ServerError("LookupUserRedirect", err)
}
} else {
ctx.ServerError("GetUserByName", err)
}
return
}
} }
// HandleOrgAssignment handles organization assignment // HandleOrgAssignment handles organization assignment
@ -77,25 +81,26 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
requireTeamAdmin = args[3] requireTeamAdmin = args[3]
} }
orgName := ctx.Params(":org")
var err error var err error
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil { if ctx.ContextUser == nil {
if organization.IsErrOrgNotExist(err) { // if Organization is not defined, get it from params
redirectUserID, err := user_model.LookupUserRedirect(orgName) if ctx.Org.Organization == nil {
if err == nil { GetOrganizationByParams(ctx)
RedirectToUser(ctx, orgName, redirectUserID) if ctx.Written() {
} else if user_model.IsErrUserRedirectNotExist(err) { return
ctx.NotFound("GetUserByName", err)
} else {
ctx.ServerError("LookupUserRedirect", err)
} }
} else {
ctx.ServerError("GetUserByName", err)
} }
} else if ctx.ContextUser.IsOrganization() {
if ctx.Org == nil {
ctx.Org = &Organization{}
}
ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
} else {
// ContextUser is an individual User
return return
} }
org := ctx.Org.Organization org := ctx.Org.Organization
// Handle Visibility // Handle Visibility
@ -156,6 +161,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
} }
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["IsPublicMember"] = func(uid int64) bool { ctx.Data["IsPublicMember"] = func(uid int64) bool {
@ -231,6 +237,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return return
} }
} }
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
} }
// OrgAssignment returns a middleware to handle organization assignment // OrgAssignment returns a middleware to handle organization assignment

View File

@ -38,6 +38,7 @@ var Log struct {
EnableAccessLog bool EnableAccessLog bool
AccessLogTemplate string AccessLogTemplate string
BufferLength int64 BufferLength int64
RequestIDHeaders []string
} }
// GetLogDescriptions returns a race safe set of descriptions // GetLogDescriptions returns a race safe set of descriptions
@ -153,6 +154,7 @@ func loadLogFrom(rootCfg ConfigProvider) {
Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString( Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString(
`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`, `{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`,
) )
Log.RequestIDHeaders = sec.Key("REQUEST_ID_HEADERS").Strings(",")
// the `MustString` updates the default value, and `log.ACCESS` is used by `generateNamedLogger("access")` later // the `MustString` updates the default value, and `log.ACCESS` is used by `generateNamedLogger("access")` later
_ = rootCfg.Section("log").Key("ACCESS").MustString("file") _ = rootCfg.Section("log").Key("ACCESS").MustString("file")

View File

@ -821,6 +821,8 @@ remove_account_link = Remove Linked Account
remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue?
remove_account_link_success = The linked account has been removed. remove_account_link_success = The linked account has been removed.
hooks.desc = Add webhooks which will be triggered for <strong>all repositories</strong> owned by this user.
orgs_none = You are not a member of any organizations. orgs_none = You are not a member of any organizations.
repos_none = You do not own any repositories repos_none = You do not own any repositories
@ -2288,6 +2290,8 @@ release.edit_subheader = Releases organize project versions.
release.tag_name = Tag name release.tag_name = Tag name
release.target = Target release.target = Target
release.tag_helper = Choose an existing tag or create a new tag. release.tag_helper = Choose an existing tag or create a new tag.
release.tag_helper_new = New tag. This tag will be created from the target.
release.tag_helper_existing = Existing tag.
release.title = Title release.title = Title
release.content = Content release.content = Content
release.prerelease_desc = Mark as Pre-Release release.prerelease_desc = Mark as Pre-Release
@ -2808,6 +2812,8 @@ auths.still_in_used = The authentication source is still in use. Convert or dele
auths.deletion_success = The authentication source has been deleted. auths.deletion_success = The authentication source has been deleted.
auths.login_source_exist = The authentication source '%s' already exists. auths.login_source_exist = The authentication source '%s' already exists.
auths.login_source_of_type_exist = An authentication source of this type already exists. auths.login_source_of_type_exist = An authentication source of this type already exists.
auths.unable_to_initialize_openid = Unable to initialize OpenID Connect Provider: %s
auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://)
config.server_config = Server Configuration config.server_config = Server Configuration
config.app_name = Site Title config.app_name = Site Title

View File

@ -334,7 +334,7 @@ non_local_account=Yerel olmayan kullanıcılar parolalarını Gitea web arayüz
verify=Doğrula verify=Doğrula
scratch_code=Çizgi kodu scratch_code=Çizgi kodu
use_scratch_code=Bir çizgi kodu kullanınız use_scratch_code=Bir çizgi kodu kullanınız
twofa_scratch_used=Çizgi kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada cihaz kaydınızı kaldırabilir veya yeni bir çizgi kodu oluşturabilirsiniz. twofa_scratch_used=Geçici kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada aygıt kaydınızı kaldırabilir veya yeni bir geçici kod oluşturabilirsiniz.
twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın. twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın.
twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir. twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir.
login_userpass=Oturum Aç login_userpass=Oturum Aç
@ -736,12 +736,12 @@ unbind=Bağlantıyı Kaldır
unbind_success=Sosyal hesabın bağlantısı Gitea hesabınızdan kaldırılmıştır. unbind_success=Sosyal hesabın bağlantısı Gitea hesabınızdan kaldırılmıştır.
manage_access_token=Erişim Jetonlarını Yönet manage_access_token=Erişim Jetonlarını Yönet
generate_new_token=Yeni Jeton Üret generate_new_token=Yeni Erişim Anahtarı Üret
tokens_desc=Bu jetonlar Gitea API'sini kullanarak hesabınıza erişim sağlar. tokens_desc=Bu jetonlar Gitea API'sini kullanarak hesabınıza erişim sağlar.
new_token_desc=Jeton kullanan uygulamalar hesabınıza tam erişime sahiptir. new_token_desc=Jeton kullanan uygulamalar hesabınıza tam erişime sahiptir.
token_name=Jeton İsmi token_name=Jeton İsmi
generate_token=Jeton Üret generate_token=Erişim Anahtarı Üret
generate_token_success=Yeni bir jeton oluşturuldu. Tekrar gösterilmeyeceği için şimdi kopyalayın. generate_token_success=Yeni bir erişim anahtarı oluşturuldu. Tekrar gösterilmeyeceği için şimdi kopyalayın.
generate_token_name_duplicate=<strong>%s</strong> zaten bir uygulama adı olarak kullanılmış. Lütfen yeni bir tane kullanın. generate_token_name_duplicate=<strong>%s</strong> zaten bir uygulama adı olarak kullanılmış. Lütfen yeni bir tane kullanın.
delete_token=Sil delete_token=Sil
access_token_deletion=Erişim Jetonunu Sil access_token_deletion=Erişim Jetonunu Sil
@ -784,12 +784,12 @@ twofa_desc=İki faktörlü kimlik doğrulama, hesabınızın güvenliğini artı
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>. twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş. twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
twofa_scratch_token_regenerate=Kazıma Belirtecini Yenile twofa_scratch_token_regenerate=Geçici Kodu Yeniden Üret
twofa_scratch_token_regenerated=Kazıma belirteciniz şimdi %s. Güvenli bir yerde saklayın. twofa_scratch_token_regenerated=Geçici kodunuz artık %s. Güvenilir bir yerde saklayın.
twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun
twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz. twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz.
twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi? twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi?
regenerate_scratch_token_desc=Karalama belirtecinizi yanlış yerleştirdiyseniz veya oturum açmak için kullandıysanız, buradan sıfırlayabilirsiniz. regenerate_scratch_token_desc=Geçici kodunuzu kaybettiyseniz veya oturum açmak için kullandıysanız, buradan sıfırlayabilirsiniz.
twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı. twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı.
scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın: scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın:
or_enter_secret=Veya gizli şeyi girin: %s or_enter_secret=Veya gizli şeyi girin: %s

View File

@ -54,7 +54,7 @@ mirror=鏡像
new_repo=新增儲存庫 new_repo=新增儲存庫
new_migrate=遷移外部儲存庫 new_migrate=遷移外部儲存庫
new_mirror=新鏡像 new_mirror=新鏡像
new_fork=新增儲存庫 fork new_fork=新增儲存庫 Fork
new_org=新增組織 new_org=新增組織
new_project=新增專案 new_project=新增專案
new_project_column=新增欄位 new_project_column=新增欄位
@ -476,6 +476,8 @@ url_error=`'%s' 是無效的 URL。`
include_error=` 必須包含子字串「%s」。 include_error=` 必須包含子字串「%s」。
glob_pattern_error=` glob 比對模式無效:%s.` glob_pattern_error=` glob 比對模式無效:%s.`
regex_pattern_error=` 正規表示式模式無效:%s.` regex_pattern_error=` 正規表示式模式無效:%s.`
username_error=`只能包含英文字母數字 ('0-9'、'a-z'、'A-Z')、破折號 ('-')、底線 ('_')、句點 ('.'),不能以非英文字母數字開頭或結尾,也不允許連續的非英文字母數字。`
invalid_group_team_map_error=` 對應無效: %s`
unknown_error=未知錯誤: unknown_error=未知錯誤:
captcha_incorrect=驗證碼不正確。 captcha_incorrect=驗證碼不正確。
password_not_match=密碼錯誤。 password_not_match=密碼錯誤。
@ -521,11 +523,11 @@ must_use_public_key=您提供的金鑰是私有金鑰,請勿上傳您的私有
unable_verify_ssh_key=無法驗證 SSH 密鑰 unable_verify_ssh_key=無法驗證 SSH 密鑰
auth_failed=授權認證失敗:%v auth_failed=授權認證失敗:%v
still_own_repo=此帳戶仍然擁有一個或多個儲存庫,您必須先刪除或轉移它們。 still_own_repo=您的帳戶擁有一個以上的儲存庫,請先刪除或轉移它們。
still_has_org=此帳戶仍是一個或多個組織的成員,您必須先離開它們。 still_has_org=您的帳戶是一個或多個組織的成員,請先離開它們。
still_own_packages=您的帳戶擁有一個或多個套件,請先刪除他們。 still_own_packages=您的帳戶擁有一個以上的套件,請先刪除它們。
org_still_own_repo=該組織仍然是某些儲存庫的擁有者,您必須先轉移或刪除它們才能執行刪除組織! org_still_own_repo=此組織仍然擁有一個以上的儲存庫,請先刪除或轉移它們。
org_still_own_packages=此組織擁有一個或多個套件,請先刪除他們。 org_still_own_packages=此組織仍然擁有一個以上的套件,請先刪除它們。
target_branch_not_exist=目標分支不存在 target_branch_not_exist=目標分支不存在
@ -1132,6 +1134,7 @@ editor.commit_directly_to_this_branch=直接提交到 <strong class="branch-name
editor.create_new_branch=為此提交建立<strong>新分支</strong>並提出合併請求。 editor.create_new_branch=為此提交建立<strong>新分支</strong>並提出合併請求。
editor.create_new_branch_np=為本次提交建立<strong>新分支</strong>。 editor.create_new_branch_np=為本次提交建立<strong>新分支</strong>。
editor.propose_file_change=提出檔案變更 editor.propose_file_change=提出檔案變更
editor.new_branch_name=命名此提交的新分支
editor.new_branch_name_desc=新的分支名稱... editor.new_branch_name_desc=新的分支名稱...
editor.cancel=取消 editor.cancel=取消
editor.filename_cannot_be_empty=檔案名稱不能為空。 editor.filename_cannot_be_empty=檔案名稱不能為空。
@ -1217,8 +1220,8 @@ projects.template.desc=範本
projects.template.desc_helper=選擇專案範本以開始 projects.template.desc_helper=選擇專案範本以開始
projects.type.uncategorized=未分類 projects.type.uncategorized=未分類
projects.column.edit=編輯欄位 projects.column.edit=編輯欄位
projects.column.edit_title=組織名稱 projects.column.edit_title=名稱
projects.column.new_title=組織名稱 projects.column.new_title=名稱
projects.column.new_submit=建立欄位 projects.column.new_submit=建立欄位
projects.column.new=新增欄位 projects.column.new=新增欄位
projects.column.set_default=設為預設 projects.column.set_default=設為預設
@ -1238,7 +1241,7 @@ issues.filter_assignees=篩選負責人
issues.filter_milestones=篩選里程碑 issues.filter_milestones=篩選里程碑
issues.filter_projects=篩選專案 issues.filter_projects=篩選專案
issues.filter_labels=篩選標籤 issues.filter_labels=篩選標籤
issues.filter_reviewers=篩選審 issues.filter_reviewers=篩選審
issues.new=新增問題 issues.new=新增問題
issues.new.title_empty=標題不可為空 issues.new.title_empty=標題不可為空
issues.new.labels=標籤 issues.new.labels=標籤
@ -1655,6 +1658,7 @@ pulls.merge_instruction_hint=`您也可以查看<a class="show-instruction">命
pulls.merge_instruction_step1_desc=在您的儲存庫中切換到新分支並測試變更。 pulls.merge_instruction_step1_desc=在您的儲存庫中切換到新分支並測試變更。
pulls.merge_instruction_step2_desc=合併變更並更新到 Gitea。 pulls.merge_instruction_step2_desc=合併變更並更新到 Gitea。
pulls.clear_merge_message=清除合併訊息 pulls.clear_merge_message=清除合併訊息
pulls.clear_merge_message_hint=清除合併訊息將僅移除提交訊息內容,留下產生的 git 結尾如「Co-Authored-By …」。
pulls.auto_merge_button_when_succeed=(當通過檢查後) pulls.auto_merge_button_when_succeed=(當通過檢查後)
pulls.auto_merge_when_succeed=通過所有檢查後自動合併 pulls.auto_merge_when_succeed=通過所有檢查後自動合併
@ -2619,7 +2623,7 @@ users.still_own_repo=這個使用者還擁有一個或更多的儲存庫。請
users.still_has_org=此使用者是組織的成員。請先將他從組織中移除。 users.still_has_org=此使用者是組織的成員。請先將他從組織中移除。
users.purge=清除使用者 users.purge=清除使用者
users.purge_help=強制刪除使用者和他擁有的所有儲存庫、組織、套件,所有留言也會被刪除。 users.purge_help=強制刪除使用者和他擁有的所有儲存庫、組織、套件,所有留言也會被刪除。
users.still_own_packages=此使用者擁有一個或多個套件,請先刪除這些套件。 users.still_own_packages=此使用者仍然擁有一個以上的套件,請先刪除這些套件。
users.deletion_success=使用者帳戶已被刪除。 users.deletion_success=使用者帳戶已被刪除。
users.reset_2fa=重設兩步驟驗證 users.reset_2fa=重設兩步驟驗證
users.list_status_filter.menu_text=篩選 users.list_status_filter.menu_text=篩選
@ -2649,7 +2653,7 @@ emails.change_email_header=更新電子信箱屬性
emails.change_email_text=您確定要更新這個電子信箱? emails.change_email_text=您確定要更新這個電子信箱?
orgs.org_manage_panel=組織管理 orgs.org_manage_panel=組織管理
orgs.name=組織名稱 orgs.name=名稱
orgs.teams=團隊數 orgs.teams=團隊數
orgs.members=成員數 orgs.members=成員數
orgs.new_orga=新增組織 orgs.new_orga=新增組織
@ -2658,7 +2662,7 @@ repos.repo_manage_panel=儲存庫管理
repos.unadopted=未接管的儲存庫 repos.unadopted=未接管的儲存庫
repos.unadopted.no_more=找不到其他未接管的儲存庫 repos.unadopted.no_more=找不到其他未接管的儲存庫
repos.owner=擁有者 repos.owner=擁有者
repos.name=儲存庫名稱 repos.name=名稱
repos.private=私有 repos.private=私有
repos.watches=關注數 repos.watches=關注數
repos.stars=星號數 repos.stars=星號數
@ -2690,8 +2694,8 @@ systemhooks.update_webhook=更新系統 Webhook
auths.auth_manage_panel=認證來源管理 auths.auth_manage_panel=認證來源管理
auths.new=新增認證來源 auths.new=新增認證來源
auths.name=認證名稱 auths.name=名稱
auths.type=認證類型 auths.type=類型
auths.enabled=已啟用 auths.enabled=已啟用
auths.syncenabled=啟用使用者同步 auths.syncenabled=啟用使用者同步
auths.updated=最後更新時間 auths.updated=最後更新時間
@ -2762,6 +2766,7 @@ auths.oauth2_required_claim_value_helper=填寫此名稱以限制 Claim 中有
auths.oauth2_group_claim_name=Claim 名稱提供群組名稱給此來源。(選用) auths.oauth2_group_claim_name=Claim 名稱提供群組名稱給此來源。(選用)
auths.oauth2_admin_group=管理員使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) auths.oauth2_admin_group=管理員使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱)
auths.oauth2_restricted_group=受限制使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱) auths.oauth2_restricted_group=受限制使用者的群組 Claim 值。(選用 - 需要上面的 Claim 名稱)
auths.oauth2_map_group_to_team=將已 Claim 的群組對應到組織團隊。(選用 - 需要上述 Claim 名稱)
auths.oauth2_map_group_to_team_removal=如果使用者不屬於相對應的群組,將使用者從已同步的團隊移除。 auths.oauth2_map_group_to_team_removal=如果使用者不屬於相對應的群組,將使用者從已同步的團隊移除。
auths.enable_auto_register=允許授權用戶自動註冊 auths.enable_auto_register=允許授權用戶自動註冊
auths.sspi_auto_create_users=自動建立使用者 auths.sspi_auto_create_users=自動建立使用者
@ -2843,7 +2848,7 @@ config.lfs_http_auth_expiry=LFS HTTP 驗證有效時間
config.db_config=資料庫組態 config.db_config=資料庫組態
config.db_type=資料庫類型 config.db_type=資料庫類型
config.db_host=主機地址 config.db_host=主機地址
config.db_name=資料庫名稱 config.db_name=名稱
config.db_user=使用者名稱 config.db_user=使用者名稱
config.db_schema=結構描述 config.db_schema=結構描述
config.db_ssl_mode=SSL config.db_ssl_mode=SSL
@ -2946,7 +2951,7 @@ config.get_setting_failed=讀取設定值 %s 失敗
config.set_setting_failed=寫入設定值 %s 失敗 config.set_setting_failed=寫入設定值 %s 失敗
monitor.cron=Cron 任務 monitor.cron=Cron 任務
monitor.name=任務名稱 monitor.name=名稱
monitor.schedule=任務安排 monitor.schedule=任務安排
monitor.next=下次執行時間 monitor.next=下次執行時間
monitor.previous=上次執行時間 monitor.previous=上次執行時間
@ -3031,7 +3036,7 @@ notices.deselect_all=取消所有選取
notices.inverse_selection=反向選取 notices.inverse_selection=反向選取
notices.delete_selected=刪除選取項 notices.delete_selected=刪除選取項
notices.delete_all=刪除所有提示 notices.delete_all=刪除所有提示
notices.type=提示類型 notices.type=類型
notices.type_1=儲存庫 notices.type_1=儲存庫
notices.type_2=任務 notices.type_2=任務
notices.desc=描述 notices.desc=描述
@ -3196,7 +3201,7 @@ generic.documentation=關於通用 registry 的詳情請參閱<a target="_blank"
helm.registry=透過下列命令設定此註冊中心: helm.registry=透過下列命令設定此註冊中心:
helm.install=執行下列命令安裝此套件: helm.install=執行下列命令安裝此套件:
helm.documentation=關於 Helm registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">說明文件</a>。 helm.documentation=關於 Helm registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/helm/">說明文件</a>。
maven.registry=在您的 <code>pom.xml</code> 檔設定此註冊中心: maven.registry=在您專案的 <code>pom.xml</code> 檔設定此註冊中心:
maven.install=若要使用此套件,請在您 <code>pom.xml</code> 檔的 <code>dependencies</code> 段落加入下列內容: maven.install=若要使用此套件,請在您 <code>pom.xml</code> 檔的 <code>dependencies</code> 段落加入下列內容:
maven.install2=透過下列命令執行: maven.install2=透過下列命令執行:
maven.download=透過下列命令下載相依性: maven.download=透過下列命令下載相依性:
@ -3205,7 +3210,7 @@ nuget.registry=透過下列命令設定此註冊中心:
nuget.install=執行下列命令以使用 NuGet 安裝此套件: nuget.install=執行下列命令以使用 NuGet 安裝此套件:
nuget.documentation=關於 NuGet registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">說明文件</a>。 nuget.documentation=關於 NuGet registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">說明文件</a>。
nuget.dependency.framework=目標框架 nuget.dependency.framework=目標框架
npm.registry=在您的 <code>.npmrc</code> 檔設定此註冊中心: npm.registry=在您專案的 <code>.npmrc</code> 檔設定此註冊中心:
npm.install=執行下列命令以使用 npm 安裝此套件: npm.install=執行下列命令以使用 npm 安裝此套件:
npm.install2=或將它加到 package.json 檔: npm.install2=或將它加到 package.json 檔:
npm.documentation=關於 npm registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">說明文件</a>。 npm.documentation=關於 npm registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">說明文件</a>。
@ -3241,6 +3246,7 @@ settings.delete.success=已刪除該套件。
settings.delete.error=刪除套件失敗。 settings.delete.error=刪除套件失敗。
owner.settings.cargo.title=Cargo Registry 索引 owner.settings.cargo.title=Cargo Registry 索引
owner.settings.cargo.initialize=初始化索引 owner.settings.cargo.initialize=初始化索引
owner.settings.cargo.initialize.description=使用 Cargo Registry 時需要一個特別的 Git 儲存庫作為索引。您可以在此使用必要的設定建立和重建它。
owner.settings.cargo.initialize.error=初始化 Cargo 索引失敗: %v owner.settings.cargo.initialize.error=初始化 Cargo 索引失敗: %v
owner.settings.cargo.initialize.success=成功建立了 Cargo 索引。 owner.settings.cargo.initialize.success=成功建立了 Cargo 索引。
owner.settings.cargo.rebuild=重建索引 owner.settings.cargo.rebuild=重建索引
@ -3252,6 +3258,7 @@ owner.settings.cleanuprules.add=加入清理規則
owner.settings.cleanuprules.edit=編輯清理規則 owner.settings.cleanuprules.edit=編輯清理規則
owner.settings.cleanuprules.none=沒有可用的清理規則。閱讀文件以了解更多。 owner.settings.cleanuprules.none=沒有可用的清理規則。閱讀文件以了解更多。
owner.settings.cleanuprules.preview=清理規則預覽 owner.settings.cleanuprules.preview=清理規則預覽
owner.settings.cleanuprules.preview.overview=已排定要移除 %d 個套件。
owner.settings.cleanuprules.preview.none=清理規則不符合任何套件。 owner.settings.cleanuprules.preview.none=清理規則不符合任何套件。
owner.settings.cleanuprules.enabled=已啟用 owner.settings.cleanuprules.enabled=已啟用
owner.settings.cleanuprules.pattern_full_match=將比對規則套用到完整的套件名稱 owner.settings.cleanuprules.pattern_full_match=將比對規則套用到完整的套件名稱
@ -3275,8 +3282,9 @@ secrets=Secret
description=Secret 會被傳給特定的 Action其他情況無法讀取。 description=Secret 會被傳給特定的 Action其他情況無法讀取。
none=還沒有 Secret。 none=還沒有 Secret。
value= value=
name=組織名稱 name=名稱
creation=加入 Secret creation=加入 Secret
creation.name_placeholder=不區分大小寫,只能包含英文字母、數字、底線 ('_'),不能以 GITEA_ 或 GITHUB_ 開頭。
creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。 creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。
creation.success=已加入 Secret「%s」。 creation.success=已加入 Secret「%s」。
creation.failed=加入 Secret 失敗。 creation.failed=加入 Secret 失敗。
@ -3305,8 +3313,8 @@ runners.new=建立 Runner
runners.new_notice=如何啟動 Runner runners.new_notice=如何啟動 Runner
runners.status=狀態 runners.status=狀態
runners.id=ID runners.id=ID
runners.name=組織名稱 runners.name=名稱
runners.owner_type=認證類型 runners.owner_type=類型
runners.description=組織描述 runners.description=組織描述
runners.labels=標籤 runners.labels=標籤
runners.last_online=最後上線時間 runners.last_online=最後上線時間

View File

@ -105,10 +105,7 @@ func CreateHook(ctx *context.APIContext) {
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.CreateHookOption) form := web.GetForm(ctx).(*api.CreateHookOption)
// TODO in body params
if !utils.CheckCreateHookOption(ctx, form) {
return
}
utils.AddSystemHook(ctx, form) utils.AddSystemHook(ctx, form)
} }

View File

@ -835,6 +835,13 @@ func Routes(ctx gocontext.Context) *web.Route {
m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches) m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches)
m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos) m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos)
m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams) m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams)
m.Group("/hooks", func() {
m.Combo("").Get(user.ListHooks).
Post(bind(api.CreateHookOption{}), user.CreateHook)
m.Combo("/{id}").Get(user.GetHook).
Patch(bind(api.EditHookOption{}), user.EditHook).
Delete(user.DeleteHook)
}, reqToken(auth_model.AccessTokenScopeAdminUserHook), reqWebhooksEnabled())
}, reqToken("")) }, reqToken(""))
// Repositories // Repositories

View File

@ -6,7 +6,6 @@ package org
import ( import (
"net/http" "net/http"
webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
@ -39,34 +38,10 @@ func ListHooks(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/HookList" // "$ref": "#/responses/HookList"
opts := &webhook_model.ListWebhookOptions{ utils.ListOwnerHooks(
ListOptions: utils.GetListOptions(ctx), ctx,
OrgID: ctx.Org.Organization.ID, ctx.ContextUser,
} )
count, err := webhook_model.CountWebhooksByOpts(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
orgHooks, err := webhook_model.ListWebhooksByOpts(ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
}
hooks := make([]*api.Hook, len(orgHooks))
for i, hook := range orgHooks {
hooks[i], err = webhook_service.ToHook(ctx.Org.Organization.AsUser().HomeLink(), hook)
if err != nil {
ctx.InternalServerError(err)
return
}
}
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, hooks)
} }
// GetHook get an organization's hook by id // GetHook get an organization's hook by id
@ -92,14 +67,12 @@ func GetHook(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
org := ctx.Org.Organization hook, err := utils.GetOwnerHook(ctx, ctx.ContextUser.ID, ctx.ParamsInt64("id"))
hookID := ctx.ParamsInt64(":id")
hook, err := utils.GetOrgHook(ctx, org.ID, hookID)
if err != nil { if err != nil {
return return
} }
apiHook, err := webhook_service.ToHook(org.AsUser().HomeLink(), hook) apiHook, err := webhook_service.ToHook(ctx.ContextUser.HomeLink(), hook)
if err != nil { if err != nil {
ctx.InternalServerError(err) ctx.InternalServerError(err)
return return
@ -131,15 +104,14 @@ func CreateHook(ctx *context.APIContext) {
// "201": // "201":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.CreateHookOption) utils.AddOwnerHook(
// TODO in body params ctx,
if !utils.CheckCreateHookOption(ctx, form) { ctx.ContextUser,
return web.GetForm(ctx).(*api.CreateHookOption),
} )
utils.AddOrgHook(ctx, form)
} }
// EditHook modify a hook of a repository // EditHook modify a hook of an organization
func EditHook(ctx *context.APIContext) { func EditHook(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook // swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook
// --- // ---
@ -168,11 +140,12 @@ func EditHook(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.EditHookOption) utils.EditOwnerHook(
ctx,
// TODO in body params ctx.ContextUser,
hookID := ctx.ParamsInt64(":id") web.GetForm(ctx).(*api.EditHookOption),
utils.EditOrgHook(ctx, form, hookID) ctx.ParamsInt64("id"),
)
} }
// DeleteHook delete a hook of an organization // DeleteHook delete a hook of an organization
@ -198,15 +171,9 @@ func DeleteHook(ctx *context.APIContext) {
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
org := ctx.Org.Organization utils.DeleteOwnerHook(
hookID := ctx.ParamsInt64(":id") ctx,
if err := webhook_model.DeleteWebhookByOrgID(org.ID, hookID); err != nil { ctx.ContextUser,
if webhook_model.IsErrWebhookNotExist(err) { ctx.ParamsInt64("id"),
ctx.NotFound() )
} else {
ctx.Error(http.StatusInternalServerError, "DeleteWebhookByOrgID", err)
}
return
}
ctx.Status(http.StatusNoContent)
} }

View File

@ -223,12 +223,8 @@ func CreateHook(ctx *context.APIContext) {
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.CreateHookOption)
if !utils.CheckCreateHookOption(ctx, form) { utils.AddRepoHook(ctx, web.GetForm(ctx).(*api.CreateHookOption))
return
}
utils.AddRepoHook(ctx, form)
} }
// EditHook modify a hook of a repository // EditHook modify a hook of a repository

154
routers/api/v1/user/hook.go Normal file
View File

@ -0,0 +1,154 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"net/http"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
webhook_service "code.gitea.io/gitea/services/webhook"
)
// ListHooks list the authenticated user's webhooks
func ListHooks(ctx *context.APIContext) {
// swagger:operation GET /user/hooks user userListHooks
// ---
// summary: List the authenticated user's webhooks
// produces:
// - application/json
// parameters:
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/HookList"
utils.ListOwnerHooks(
ctx,
ctx.Doer,
)
}
// GetHook get the authenticated user's hook by id
func GetHook(ctx *context.APIContext) {
// swagger:operation GET /user/hooks/{id} user userGetHook
// ---
// summary: Get a hook
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of the hook to get
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/Hook"
hook, err := utils.GetOwnerHook(ctx, ctx.Doer.ID, ctx.ParamsInt64("id"))
if err != nil {
return
}
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
if err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(http.StatusOK, apiHook)
}
// CreateHook create a hook for the authenticated user
func CreateHook(ctx *context.APIContext) {
// swagger:operation POST /user/hooks user userCreateHook
// ---
// summary: Create a hook
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/CreateHookOption"
// responses:
// "201":
// "$ref": "#/responses/Hook"
utils.AddOwnerHook(
ctx,
ctx.Doer,
web.GetForm(ctx).(*api.CreateHookOption),
)
}
// EditHook modify a hook of the authenticated user
func EditHook(ctx *context.APIContext) {
// swagger:operation PATCH /user/hooks/{id} user userEditHook
// ---
// summary: Update a hook
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of the hook to update
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditHookOption"
// responses:
// "200":
// "$ref": "#/responses/Hook"
utils.EditOwnerHook(
ctx,
ctx.Doer,
web.GetForm(ctx).(*api.EditHookOption),
ctx.ParamsInt64("id"),
)
}
// DeleteHook delete a hook of the authenticated user
func DeleteHook(ctx *context.APIContext) {
// swagger:operation DELETE /user/hooks/{id} user userDeleteHook
// ---
// summary: Delete a hook
// produces:
// - application/json
// parameters:
// - name: id
// in: path
// description: id of the hook to delete
// type: integer
// format: int64
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
utils.DeleteOwnerHook(
ctx,
ctx.Doer,
ctx.ParamsInt64("id"),
)
}

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"strings" "strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
@ -18,15 +19,46 @@ import (
webhook_service "code.gitea.io/gitea/services/webhook" webhook_service "code.gitea.io/gitea/services/webhook"
) )
// GetOrgHook get an organization's webhook. If there is an error, write to // ListOwnerHooks lists the webhooks of the provided owner
// `ctx` accordingly and return the error func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
func GetOrgHook(ctx *context.APIContext, orgID, hookID int64) (*webhook.Webhook, error) { opts := &webhook.ListWebhookOptions{
w, err := webhook.GetWebhookByOrgID(orgID, hookID) ListOptions: GetListOptions(ctx),
OwnerID: owner.ID,
}
count, err := webhook.CountWebhooksByOpts(opts)
if err != nil {
ctx.InternalServerError(err)
return
}
hooks, err := webhook.ListWebhooksByOpts(ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
}
apiHooks := make([]*api.Hook, len(hooks))
for i, hook := range hooks {
apiHooks[i], err = webhook_service.ToHook(owner.HomeLink(), hook)
if err != nil {
ctx.InternalServerError(err)
return
}
}
ctx.SetTotalCountHeader(count)
ctx.JSON(http.StatusOK, apiHooks)
}
// GetOwnerHook gets an user or organization webhook. Errors are written to ctx.
func GetOwnerHook(ctx *context.APIContext, ownerID, hookID int64) (*webhook.Webhook, error) {
w, err := webhook.GetWebhookByOwnerID(ownerID, hookID)
if err != nil { if err != nil {
if webhook.IsErrWebhookNotExist(err) { if webhook.IsErrWebhookNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(http.StatusInternalServerError, "GetWebhookByOrgID", err) ctx.Error(http.StatusInternalServerError, "GetWebhookByOwnerID", err)
} }
return nil, err return nil, err
} }
@ -48,9 +80,9 @@ func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhoo
return w, nil return w, nil
} }
// CheckCreateHookOption check if a CreateHookOption form is valid. If invalid, // checkCreateHookOption check if a CreateHookOption form is valid. If invalid,
// write the appropriate error to `ctx`. Return whether the form is valid // write the appropriate error to `ctx`. Return whether the form is valid
func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool { func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool {
if !webhook_service.IsValidHookTaskType(form.Type) { if !webhook_service.IsValidHookTaskType(form.Type) {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Invalid hook type: %s", form.Type)) ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Invalid hook type: %s", form.Type))
return false return false
@ -81,14 +113,13 @@ func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) {
} }
} }
// AddOrgHook add a hook to an organization. Writes to `ctx` accordingly // AddOwnerHook adds a hook to an user or organization
func AddOrgHook(ctx *context.APIContext, form *api.CreateHookOption) { func AddOwnerHook(ctx *context.APIContext, owner *user_model.User, form *api.CreateHookOption) {
org := ctx.Org.Organization hook, ok := addHook(ctx, form, owner.ID, 0)
hook, ok := addHook(ctx, form, org.ID, 0)
if !ok { if !ok {
return return
} }
apiHook, ok := toAPIHook(ctx, org.AsUser().HomeLink(), hook) apiHook, ok := toAPIHook(ctx, owner.HomeLink(), hook)
if !ok { if !ok {
return return
} }
@ -128,14 +159,18 @@ func pullHook(events []string, event string) bool {
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true) return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
} }
// addHook add the hook specified by `form`, `orgID` and `repoID`. If there is // addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
// an error, write to `ctx` accordingly. Return (webhook, ok) // an error, write to `ctx` accordingly. Return (webhook, ok)
func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID int64) (*webhook.Webhook, bool) { func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
if !checkCreateHookOption(ctx, form) {
return nil, false
}
if len(form.Events) == 0 { if len(form.Events) == 0 {
form.Events = []string{"push"} form.Events = []string{"push"}
} }
w := &webhook.Webhook{ w := &webhook.Webhook{
OrgID: orgID, OwnerID: ownerID,
RepoID: repoID, RepoID: repoID,
URL: form.Config["url"], URL: form.Config["url"],
ContentType: webhook.ToHookContentType(form.Config["content_type"]), ContentType: webhook.ToHookContentType(form.Config["content_type"]),
@ -234,21 +269,20 @@ func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID in
ctx.JSON(http.StatusOK, h) ctx.JSON(http.StatusOK, h)
} }
// EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly // EditOwnerHook updates a webhook of an user or organization
func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { func EditOwnerHook(ctx *context.APIContext, owner *user_model.User, form *api.EditHookOption, hookID int64) {
org := ctx.Org.Organization hook, err := GetOwnerHook(ctx, owner.ID, hookID)
hook, err := GetOrgHook(ctx, org.ID, hookID)
if err != nil { if err != nil {
return return
} }
if !editHook(ctx, form, hook) { if !editHook(ctx, form, hook) {
return return
} }
updated, err := GetOrgHook(ctx, org.ID, hookID) updated, err := GetOwnerHook(ctx, owner.ID, hookID)
if err != nil { if err != nil {
return return
} }
apiHook, ok := toAPIHook(ctx, org.AsUser().HomeLink(), updated) apiHook, ok := toAPIHook(ctx, owner.HomeLink(), updated)
if !ok { if !ok {
return return
} }
@ -362,3 +396,16 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
} }
return true return true
} }
// DeleteOwnerHook deletes the hook owned by the owner.
func DeleteOwnerHook(ctx *context.APIContext, owner *user_model.User, hookID int64) {
if err := webhook.DeleteWebhookByOwnerID(owner.ID, hookID); err != nil {
if webhook.IsErrWebhookNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "DeleteWebhookByOwnerID", err)
}
return
}
ctx.Status(http.StatusNoContent)
}

View File

@ -271,6 +271,15 @@ func NewAuthSourcePost(ctx *context.Context) {
} }
case auth.OAuth2: case auth.OAuth2:
config = parseOAuth2Config(form) config = parseOAuth2Config(form)
oauth2Config := config.(*oauth2.Source)
if oauth2Config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
ctx.Data["Err_DiscoveryURL"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthNew, form)
return
}
}
case auth.SSPI: case auth.SSPI:
var err error var err error
config, err = parseSSPIConfig(ctx, form) config, err = parseSSPIConfig(ctx, form)
@ -305,6 +314,10 @@ func NewAuthSourcePost(ctx *context.Context) {
if auth.IsErrSourceAlreadyExist(err) { if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form) ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form)
} else if oauth2.IsErrOpenIDConnectInitialize(err) {
ctx.Data["Err_DiscoveryURL"] = true
unwrapped := err.(oauth2.ErrOpenIDConnectInitialize).Unwrap()
ctx.RenderWithErr(ctx.Tr("admin.auths.unable_to_initialize_openid", unwrapped), tplAuthNew, form)
} else { } else {
ctx.ServerError("auth.CreateSource", err) ctx.ServerError("auth.CreateSource", err)
} }
@ -389,6 +402,15 @@ func EditAuthSourcePost(ctx *context.Context) {
} }
case auth.OAuth2: case auth.OAuth2:
config = parseOAuth2Config(form) config = parseOAuth2Config(form)
oauth2Config := config.(*oauth2.Source)
if oauth2Config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
ctx.Data["Err_DiscoveryURL"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthEdit, form)
return
}
}
case auth.SSPI: case auth.SSPI:
config, err = parseSSPIConfig(ctx, form) config, err = parseSSPIConfig(ctx, form)
if err != nil { if err != nil {
@ -408,6 +430,7 @@ func EditAuthSourcePost(ctx *context.Context) {
if err := auth.UpdateSource(source); err != nil { if err := auth.UpdateSource(source); err != nil {
if oauth2.IsErrOpenIDConnectInitialize(err) { if oauth2.IsErrOpenIDConnectInitialize(err) {
ctx.Flash.Error(err.Error(), true) ctx.Flash.Error(err.Error(), true)
ctx.Data["Err_DiscoveryURL"] = true
ctx.HTML(http.StatusOK, tplAuthEdit) ctx.HTML(http.StatusOK, tplAuthEdit)
} else { } else {
ctx.ServerError("UpdateSource", err) ctx.ServerError("UpdateSource", err)

View File

@ -156,6 +156,7 @@ func Home(ctx *context.Context) {
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "language", "Language") pager.AddParam(ctx, "language", "Language")
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.HTML(http.StatusOK, tplOrgHome) ctx.HTML(http.StatusOK, tplOrgHome)
} }

View File

@ -123,6 +123,7 @@ func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new") ctx.Data["Title"] = ctx.Tr("repo.projects.new")
ctx.Data["BoardTypes"] = project_model.GetBoardConfig() ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
ctx.Data["PageIsViewProjects"] = true
ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink() ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink()
shared_user.RenderUserHeader(ctx) shared_user.RenderUserHeader(ctx)
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)

View File

@ -218,9 +218,9 @@ func Webhooks(ctx *context.Context) {
ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks" ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks"
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc") ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{OrgID: ctx.Org.Organization.ID}) ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID})
if err != nil { if err != nil {
ctx.ServerError("GetWebhooksByOrgId", err) ctx.ServerError("ListWebhooksByOpts", err)
return return
} }
@ -230,8 +230,8 @@ func Webhooks(ctx *context.Context) {
// DeleteWebhook response for delete webhook // DeleteWebhook response for delete webhook
func DeleteWebhook(ctx *context.Context) { func DeleteWebhook(ctx *context.Context) {
if err := webhook.DeleteWebhookByOrgID(ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { if err := webhook.DeleteWebhookByOwnerID(ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteWebhookByOrgID: " + err.Error()) ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error())
} else { } else {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
} }

View File

@ -33,6 +33,7 @@ const (
tplHooks base.TplName = "repo/settings/webhook/base" tplHooks base.TplName = "repo/settings/webhook/base"
tplHookNew base.TplName = "repo/settings/webhook/new" tplHookNew base.TplName = "repo/settings/webhook/new"
tplOrgHookNew base.TplName = "org/settings/hook_new" tplOrgHookNew base.TplName = "org/settings/hook_new"
tplUserHookNew base.TplName = "user/settings/hook_new"
tplAdminHookNew base.TplName = "admin/hook_new" tplAdminHookNew base.TplName = "admin/hook_new"
) )
@ -54,8 +55,8 @@ func Webhooks(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplHooks) ctx.HTML(http.StatusOK, tplHooks)
} }
type orgRepoCtx struct { type ownerRepoCtx struct {
OrgID int64 OwnerID int64
RepoID int64 RepoID int64
IsAdmin bool IsAdmin bool
IsSystemWebhook bool IsSystemWebhook bool
@ -64,10 +65,10 @@ type orgRepoCtx struct {
NewTemplate base.TplName NewTemplate base.TplName
} }
// getOrgRepoCtx determines whether this is a repo, organization, or admin (both default and system) context. // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context.
func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) { func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
if len(ctx.Repo.RepoLink) > 0 { if is, ok := ctx.Data["IsRepositoryWebhook"]; ok && is.(bool) {
return &orgRepoCtx{ return &ownerRepoCtx{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Link: path.Join(ctx.Repo.RepoLink, "settings/hooks"), Link: path.Join(ctx.Repo.RepoLink, "settings/hooks"),
LinkNew: path.Join(ctx.Repo.RepoLink, "settings/hooks"), LinkNew: path.Join(ctx.Repo.RepoLink, "settings/hooks"),
@ -75,37 +76,35 @@ func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) {
}, nil }, nil
} }
if len(ctx.Org.OrgLink) > 0 { if is, ok := ctx.Data["IsOrganizationWebhook"]; ok && is.(bool) {
return &orgRepoCtx{ return &ownerRepoCtx{
OrgID: ctx.Org.Organization.ID, OwnerID: ctx.ContextUser.ID,
Link: path.Join(ctx.Org.OrgLink, "settings/hooks"), Link: path.Join(ctx.Org.OrgLink, "settings/hooks"),
LinkNew: path.Join(ctx.Org.OrgLink, "settings/hooks"), LinkNew: path.Join(ctx.Org.OrgLink, "settings/hooks"),
NewTemplate: tplOrgHookNew, NewTemplate: tplOrgHookNew,
}, nil }, nil
} }
if ctx.Doer.IsAdmin { if is, ok := ctx.Data["IsUserWebhook"]; ok && is.(bool) {
// Are we looking at default webhooks? return &ownerRepoCtx{
if ctx.Params(":configType") == "default-hooks" { OwnerID: ctx.Doer.ID,
return &orgRepoCtx{ Link: path.Join(setting.AppSubURL, "/user/settings/hooks"),
IsAdmin: true, LinkNew: path.Join(setting.AppSubURL, "/user/settings/hooks"),
Link: path.Join(setting.AppSubURL, "/admin/hooks"), NewTemplate: tplUserHookNew,
LinkNew: path.Join(setting.AppSubURL, "/admin/default-hooks"), }, nil
NewTemplate: tplAdminHookNew, }
}, nil
}
// Must be system webhooks instead if ctx.Doer.IsAdmin {
return &orgRepoCtx{ return &ownerRepoCtx{
IsAdmin: true, IsAdmin: true,
IsSystemWebhook: true, IsSystemWebhook: ctx.Params(":configType") == "system-hooks",
Link: path.Join(setting.AppSubURL, "/admin/hooks"), Link: path.Join(setting.AppSubURL, "/admin/hooks"),
LinkNew: path.Join(setting.AppSubURL, "/admin/system-hooks"), LinkNew: path.Join(setting.AppSubURL, "/admin/system-hooks"),
NewTemplate: tplAdminHookNew, NewTemplate: tplAdminHookNew,
}, nil }, nil
} }
return nil, errors.New("unable to set OrgRepo context") return nil, errors.New("unable to set OwnerRepo context")
} }
func checkHookType(ctx *context.Context) string { func checkHookType(ctx *context.Context) string {
@ -122,9 +121,9 @@ func WebhooksNew(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
orCtx, err := getOrgRepoCtx(ctx) orCtx, err := getOwnerRepoCtx(ctx)
if err != nil { if err != nil {
ctx.ServerError("getOrgRepoCtx", err) ctx.ServerError("getOwnerRepoCtx", err)
return return
} }
@ -205,9 +204,9 @@ func createWebhook(ctx *context.Context, params webhookParams) {
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}} ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
ctx.Data["HookType"] = params.Type ctx.Data["HookType"] = params.Type
orCtx, err := getOrgRepoCtx(ctx) orCtx, err := getOwnerRepoCtx(ctx)
if err != nil { if err != nil {
ctx.ServerError("getOrgRepoCtx", err) ctx.ServerError("getOwnerRepoCtx", err)
return return
} }
ctx.Data["BaseLink"] = orCtx.LinkNew ctx.Data["BaseLink"] = orCtx.LinkNew
@ -236,7 +235,7 @@ func createWebhook(ctx *context.Context, params webhookParams) {
IsActive: params.WebhookForm.Active, IsActive: params.WebhookForm.Active,
Type: params.Type, Type: params.Type,
Meta: string(meta), Meta: string(meta),
OrgID: orCtx.OrgID, OwnerID: orCtx.OwnerID,
IsSystemWebhook: orCtx.IsSystemWebhook, IsSystemWebhook: orCtx.IsSystemWebhook,
} }
err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader)
@ -577,19 +576,19 @@ func packagistHookParams(ctx *context.Context) webhookParams {
} }
} }
func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
orCtx, err := getOrgRepoCtx(ctx) orCtx, err := getOwnerRepoCtx(ctx)
if err != nil { if err != nil {
ctx.ServerError("getOrgRepoCtx", err) ctx.ServerError("getOwnerRepoCtx", err)
return nil, nil return nil, nil
} }
ctx.Data["BaseLink"] = orCtx.Link ctx.Data["BaseLink"] = orCtx.Link
var w *webhook.Webhook var w *webhook.Webhook
if orCtx.RepoID > 0 { if orCtx.RepoID > 0 {
w, err = webhook.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) w, err = webhook.GetWebhookByRepoID(orCtx.RepoID, ctx.ParamsInt64(":id"))
} else if orCtx.OrgID > 0 { } else if orCtx.OwnerID > 0 {
w, err = webhook.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) w, err = webhook.GetWebhookByOwnerID(orCtx.OwnerID, ctx.ParamsInt64(":id"))
} else if orCtx.IsAdmin { } else if orCtx.IsAdmin {
w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id")) w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id"))
} }

View File

@ -9,6 +9,8 @@ import (
) )
func RenderUserHeader(ctx *context.Context) { func RenderUserHeader(ctx *context.Context) {
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["ContextUser"] = ctx.ContextUser
} }

View File

@ -24,6 +24,7 @@ func CodeSearch(ctx *context.Context) {
return return
} }
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore.code") ctx.Data["Title"] = ctx.Tr("explore.code")

View File

@ -304,6 +304,7 @@ func Profile(ctx *context.Context) {
pager.AddParam(ctx, "date", "Date") pager.AddParam(ctx, "date", "Date")
} }
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.Data["IsProjectEnabled"] = true
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

View File

@ -0,0 +1,48 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"net/http"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
)
const (
tplSettingsHooks base.TplName = "user/settings/hooks"
)
// Webhooks render webhook list page
func Webhooks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})
if err != nil {
ctx.ServerError("ListWebhooksByOpts", err)
return
}
ctx.Data["Webhooks"] = ws
ctx.HTML(http.StatusOK, tplSettingsHooks)
}
// DeleteWebhook response for delete webhook
func DeleteWebhook(ctx *context.Context) {
if err := webhook.DeleteWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"redirect": setting.AppSubURL + "/user/settings/hooks",
})
}

View File

@ -315,6 +315,35 @@ func RegisterRoutes(m *web.Route) {
} }
} }
addWebhookAddRoutes := func() {
m.Get("/{type}/new", repo.WebhooksNew)
m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
}
addWebhookEditRoutes := func() {
m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost)
m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
}
// FIXME: not all routes need go through same middleware. // FIXME: not all routes need go through same middleware.
// Especially some AJAX requests, we can reduce middleware number to improve performance. // Especially some AJAX requests, we can reduce middleware number to improve performance.
// Routers. // Routers.
@ -482,6 +511,19 @@ func RegisterRoutes(m *web.Route) {
m.Get("/organization", user_setting.Organization) m.Get("/organization", user_setting.Organization)
m.Get("/repos", user_setting.Repos) m.Get("/repos", user_setting.Repos)
m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository)
m.Group("/hooks", func() {
m.Get("", user_setting.Webhooks)
m.Post("/delete", user_setting.DeleteWebhook)
addWebhookAddRoutes()
m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/replay/{uuid}", repo.ReplayWebhook)
})
addWebhookEditRoutes()
}, webhooksEnabled, func(ctx *context.Context) {
ctx.Data["IsUserWebhook"] = true
})
}, reqSignIn, func(ctx *context.Context) { }, reqSignIn, func(ctx *context.Context) {
ctx.Data["PageIsUserSettings"] = true ctx.Data["PageIsUserSettings"] = true
ctx.Data["AllThemes"] = setting.UI.Themes ctx.Data["AllThemes"] = setting.UI.Themes
@ -575,32 +617,11 @@ func RegisterRoutes(m *web.Route) {
m.Get("", repo.WebHooksEdit) m.Get("", repo.WebHooksEdit)
m.Post("/replay/{uuid}", repo.ReplayWebhook) m.Post("/replay/{uuid}", repo.ReplayWebhook)
}) })
m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) addWebhookEditRoutes()
m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
}, webhooksEnabled) }, webhooksEnabled)
m.Group("/{configType:default-hooks|system-hooks}", func() { m.Group("/{configType:default-hooks|system-hooks}", func() {
m.Get("/{type}/new", repo.WebhooksNew) addWebhookAddRoutes()
m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
}) })
m.Group("/auths", func() { m.Group("/auths", func() {
@ -690,6 +711,21 @@ func RegisterRoutes(m *web.Route) {
} }
} }
reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode) func(ctx *context.Context) {
return func(ctx *context.Context) {
if ctx.ContextUser == nil {
ctx.NotFound(unitType.String(), nil)
return
}
if ctx.ContextUser.IsOrganization() {
if ctx.Org.Organization.UnitPermission(ctx, ctx.Doer, unitType) < accessMode {
ctx.NotFound(unitType.String(), nil)
return
}
}
}
}
// ***** START: Organization ***** // ***** START: Organization *****
m.Group("/org", func() { m.Group("/org", func() {
m.Group("/{org}", func() { m.Group("/{org}", func() {
@ -759,32 +795,15 @@ func RegisterRoutes(m *web.Route) {
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Get("", org.Webhooks) m.Get("", org.Webhooks)
m.Post("/delete", org.DeleteWebhook) m.Post("/delete", org.DeleteWebhook)
m.Get("/{type}/new", repo.WebhooksNew) addWebhookAddRoutes()
m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit) m.Get("", repo.WebHooksEdit)
m.Post("/replay/{uuid}", repo.ReplayWebhook) m.Post("/replay/{uuid}", repo.ReplayWebhook)
}) })
m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) addWebhookEditRoutes()
m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) }, webhooksEnabled, func(ctx *context.Context) {
m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) ctx.Data["IsOrganizationWebhook"] = true
m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) })
m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
}, webhooksEnabled)
m.Group("/labels", func() { m.Group("/labels", func() {
m.Get("", org.RetrieveLabels, org.Labels) m.Get("", org.RetrieveLabels, org.Labels)
@ -869,8 +888,10 @@ func RegisterRoutes(m *web.Route) {
} }
m.Group("/projects", func() { m.Group("/projects", func() {
m.Get("", org.Projects) m.Group("", func() {
m.Get("/{id}", org.ViewProject) m.Get("", org.Projects)
m.Get("/{id}", org.ViewProject)
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead))
m.Group("", func() { //nolint:dupl m.Group("", func() { //nolint:dupl
m.Get("/new", org.NewProject) m.Get("/new", org.NewProject)
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost) m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
@ -890,25 +911,18 @@ func RegisterRoutes(m *web.Route) {
m.Post("/move", org.MoveIssues) m.Post("/move", org.MoveIssues)
}) })
}) })
}, reqSignIn, func(ctx *context.Context) { }, reqSignIn, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite), func(ctx *context.Context) {
if ctx.ContextUser == nil { if ctx.ContextUser.IsIndividual() && ctx.ContextUser.ID != ctx.Doer.ID {
ctx.NotFound("NewProject", nil)
return
}
if ctx.ContextUser.IsOrganization() {
if !ctx.Org.CanWriteUnit(ctx, unit.TypeProjects) {
ctx.NotFound("NewProject", nil)
return
}
} else if ctx.ContextUser.ID != ctx.Doer.ID {
ctx.NotFound("NewProject", nil) ctx.NotFound("NewProject", nil)
return return
} }
}) })
}, repo.MustEnableProjects) }, repo.MustEnableProjects)
m.Get("/code", user.CodeSearch) m.Group("", func() {
}, context_service.UserAssignmentWeb()) m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead))
}, context_service.UserAssignmentWeb(), context.OrgAssignment())
// ***** Release Attachment Download without Signin // ***** Release Attachment Download without Signin
m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload) m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload)
@ -962,35 +976,16 @@ func RegisterRoutes(m *web.Route) {
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Get("", repo.Webhooks) m.Get("", repo.Webhooks)
m.Post("/delete", repo.DeleteWebhook) m.Post("/delete", repo.DeleteWebhook)
m.Get("/{type}/new", repo.WebhooksNew) addWebhookAddRoutes()
m.Post("/gitea/new", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Post("/telegram/new", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
m.Group("/{id}", func() { m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit) m.Get("", repo.WebHooksEdit)
m.Post("/test", repo.TestWebhook) m.Post("/test", repo.TestWebhook)
m.Post("/replay/{uuid}", repo.ReplayWebhook) m.Post("/replay/{uuid}", repo.ReplayWebhook)
}) })
m.Post("/gitea/{id}", web.Bind(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) addWebhookEditRoutes()
m.Post("/gogs/{id}", web.Bind(forms.NewGogshookForm{}), repo.GogsHooksEditPost) }, webhooksEnabled, func(ctx *context.Context) {
m.Post("/slack/{id}", web.Bind(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) ctx.Data["IsRepositoryWebhook"] = true
m.Post("/discord/{id}", web.Bind(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) })
m.Post("/dingtalk/{id}", web.Bind(forms.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Post("/telegram/{id}", web.Bind(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", web.Bind(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", web.Bind(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", web.Bind(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", web.Bind(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
}, webhooksEnabled)
m.Group("/keys", func() { m.Group("/keys", func() {
m.Combo("").Get(repo.DeployKeys). m.Combo("").Get(repo.DeployKeys).

View File

@ -36,6 +36,10 @@ func (err ErrOpenIDConnectInitialize) Error() string {
return fmt.Sprintf("Failed to initialize OpenID Connect Provider with name '%s' with url '%s': %v", err.ProviderName, err.OpenIDConnectAutoDiscoveryURL, err.Cause) return fmt.Sprintf("Failed to initialize OpenID Connect Provider with name '%s' with url '%s': %v", err.ProviderName, err.OpenIDConnectAutoDiscoveryURL, err.Cause)
} }
func (err ErrOpenIDConnectInitialize) Unwrap() error {
return err.Cause
}
// wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2 // wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2
// inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models // inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models
func wrapOpenIDConnectInitializeError(err error, providerName string, source *Source) error { func wrapOpenIDConnectInitializeError(err error, providerName string, source *Source) error {

View File

@ -8,7 +8,6 @@ import (
"net/http" "net/http"
"strings" "strings"
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
) )
@ -57,14 +56,6 @@ func userAssignment(ctx *context.Context, errCb func(int, string, interface{}))
} else { } else {
errCb(http.StatusInternalServerError, "GetUserByName", err) errCb(http.StatusInternalServerError, "GetUserByName", err)
} }
} else {
if ctx.ContextUser.IsOrganization() {
if ctx.Org == nil {
ctx.Org = &context.Organization{}
}
ctx.Org.Organization = (*org_model.Organization)(ctx.ContextUser)
ctx.Data["Org"] = ctx.Org.Organization
}
} }
} }
} }

View File

@ -101,7 +101,7 @@ func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_mode
HookEvent: templateWebhook.HookEvent, HookEvent: templateWebhook.HookEvent,
IsActive: templateWebhook.IsActive, IsActive: templateWebhook.IsActive,
Type: templateWebhook.Type, Type: templateWebhook.Type,
OrgID: templateWebhook.OrgID, OwnerID: templateWebhook.OwnerID,
Events: templateWebhook.Events, Events: templateWebhook.Events,
Meta: templateWebhook.Meta, Meta: templateWebhook.Meta,
}) })

View File

@ -229,16 +229,16 @@ func PrepareWebhooks(ctx context.Context, source EventSource, event webhook_modu
owner = source.Repository.MustOwner(ctx) owner = source.Repository.MustOwner(ctx)
} }
// check if owner is an org and append additional webhooks // append additional webhooks of a user or organization
if owner != nil && owner.IsOrganization() { if owner != nil {
orgHooks, err := webhook_model.ListWebhooksByOpts(ctx, &webhook_model.ListWebhookOptions{ ownerHooks, err := webhook_model.ListWebhooksByOpts(ctx, &webhook_model.ListWebhookOptions{
OrgID: owner.ID, OwnerID: owner.ID,
IsActive: util.OptionalBoolTrue, IsActive: util.OptionalBoolTrue,
}) })
if err != nil { if err != nil {
return fmt.Errorf("ListWebhooksByOpts: %w", err) return fmt.Errorf("ListWebhooksByOpts: %w", err)
} }
ws = append(ws, orgHooks...) ws = append(ws, ownerHooks...)
} }
// Add any admin-defined system webhooks // Add any admin-defined system webhooks

View File

@ -24,7 +24,7 @@
<label for="oauth2_icon_url">{{.locale.Tr "admin.auths.oauth2_icon_url"}}</label> <label for="oauth2_icon_url">{{.locale.Tr "admin.auths.oauth2_icon_url"}}</label>
<input id="oauth2_icon_url" name="oauth2_icon_url" value="{{.oauth2_icon_url}}"> <input id="oauth2_icon_url" name="oauth2_icon_url" value="{{.oauth2_icon_url}}">
</div> </div>
<div class="open_id_connect_auto_discovery_url required field"> <div class="open_id_connect_auto_discovery_url required field{{if .Err_DiscoveryURL}} error{{end}}">
<label for="open_id_connect_auto_discovery_url">{{.locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label> <label for="open_id_connect_auto_discovery_url">{{.locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label>
<input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{.open_id_connect_auto_discovery_url}}"> <input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{.open_id_connect_auto_discovery_url}}">
</div> </div>

View File

@ -3,16 +3,18 @@
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}"> <a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}} {{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a> </a>
{{if and .IsProjectEnabled .CanReadProjects}}
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects"> <a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}} {{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a> </a>
{{if .IsPackageEnabled}} {{end}}
{{if and .IsPackageEnabled .CanReadPackages}}
<a class="item" href="{{$.Org.HomeLink}}/-/packages"> <a class="item" href="{{$.Org.HomeLink}}/-/packages">
{{svg "octicon-package"}} {{.locale.Tr "packages.title"}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}}
</a> </a>
{{end}} {{end}}
{{if .IsRepoIndexerEnabled}} {{if and .IsRepoIndexerEnabled .CanReadCode}}
<a class="{{if $.PageIsOrgCode}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code"> <a class="item" href="{{$.Org.HomeLink}}/-/code">
{{svg "octicon-code"}}&nbsp;{{$.locale.Tr "org.code"}} {{svg "octicon-code"}}&nbsp;{{$.locale.Tr "org.code"}}
</a> </a>
{{end}} {{end}}

View File

@ -20,22 +20,27 @@
<b>{{.tag_name}}</b><span class="at">@</span><strong>{{.tag_target}}</strong> <b>{{.tag_name}}</b><span class="at">@</span><strong>{{.tag_target}}</strong>
{{else}} {{else}}
<input id="tag-name" name="tag_name" value="{{.tag_name}}" placeholder="{{.locale.Tr "repo.release.tag_name"}}" autofocus required maxlength="255"> <input id="tag-name" name="tag_name" value="{{.tag_name}}" placeholder="{{.locale.Tr "repo.release.tag_name"}}" autofocus required maxlength="255">
<span class="at">@</span> <input id="tag-name-editor" type="hidden" data-existing-tags={{Json .Tags}} data-tag-helper={{.locale.Tr "repo.release.tag_helper"}} data-tag-helper-new={{.locale.Tr "repo.release.tag_helper_new"}} data-tag-helper-existing={{.locale.Tr "repo.release.tag_helper_existing"}} />
<div class="ui selection dropdown"> <div id="tag-target-selector" class="gt-dib">
<input type="hidden" name="tag_target" value="{{.tag_target}}"/> <span class="at">@</span>
{{svg "octicon-git-branch"}} <div class="ui selection dropdown">
<div class="text"> <input type="hidden" name="tag_target" value="{{.tag_target}}"/>
{{.locale.Tr "repo.release.target"}} : {{svg "octicon-git-branch"}}
<strong id="repo-branch-current">{{.Repository.DefaultBranch}}</strong> <div class="text">
</div> {{.locale.Tr "repo.release.target"}} :
{{svg "octicon-triangle-down" 14 "dropdown icon"}} <strong id="repo-branch-current">{{.Repository.DefaultBranch}}</strong>
<div class="menu"> </div>
{{range .Branches}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="item" data-value="{{.}}">{{.}}</div> <div class="menu">
{{end}} {{range .Branches}}
<div class="item" data-value="{{.}}">{{.}}</div>
{{end}}
</div>
</div> </div>
</div> </div>
<span class="help">{{.locale.Tr "repo.release.tag_helper"}}</span> <div>
<span id="tag-helper" class="help gt-mt-2">{{.locale.Tr "repo.release.tag_helper"}}</span>
</div>
{{end}} {{end}}
</div> </div>
</div> </div>

View File

@ -40,7 +40,7 @@
<span class="ui label">N/A</span> <span class="ui label">N/A</span>
{{end}} {{end}}
</a> </a>
{{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin}} {{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin $.PageIsUserSettings}}
<div class="right menu"> <div class="right menu">
<form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post"> <form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}

View File

@ -13014,6 +13014,152 @@
} }
} }
}, },
"/user/hooks": {
"get": {
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "List the authenticated user's webhooks",
"operationId": "userListHooks",
"parameters": [
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/HookList"
}
}
},
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Create a hook",
"operationId": "userCreateHook",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/CreateHookOption"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/Hook"
}
}
}
},
"/user/hooks/{id}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Get a hook",
"operationId": "userGetHook",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "id of the hook to get",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/Hook"
}
}
},
"delete": {
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Delete a hook",
"operationId": "userDeleteHook",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "id of the hook to delete",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
}
}
},
"patch": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Update a hook",
"operationId": "userEditHook",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "id of the hook to update",
"name": "id",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/EditHookOption"
}
}
],
"responses": {
"200": {
"$ref": "#/responses/Hook"
}
}
}
},
"/user/keys": { "/user/keys": {
"get": { "get": {
"produces": [ "produces": [

View File

@ -22,15 +22,17 @@
<a class="item" href="{{.ContextUser.HomeLink}}"> <a class="item" href="{{.ContextUser.HomeLink}}">
{{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}} {{svg "octicon-repo"}} {{.locale.Tr "user.repositories"}}
</a> </a>
{{if and .IsProjectEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadProjects))}}
<a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item"> <a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item">
{{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}} {{svg "octicon-project-symlink"}} {{.locale.Tr "user.projects"}}
</a> </a>
{{if (not .UnitPackagesGlobalDisabled)}} {{end}}
{{if and .IsPackageEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadPackages))}}
<a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item"> <a href="{{.ContextUser.HomeLink}}/-/packages" class="{{if .IsPackagesPage}}active {{end}}item">
{{svg "octicon-package"}} {{.locale.Tr "packages.title"}} {{svg "octicon-package"}} {{.locale.Tr "packages.title"}}
</a> </a>
{{end}} {{end}}
{{if .IsRepoIndexerEnabled}} {{if and .IsRepoIndexerEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadCode))}}
<a href="{{.ContextUser.HomeLink}}/-/code" class="{{if .IsCodePage}}active {{end}}item"> <a href="{{.ContextUser.HomeLink}}/-/code" class="{{if .IsCodePage}}active {{end}}item">
{{svg "octicon-code"}} {{.locale.Tr "user.code"}} {{svg "octicon-code"}} {{.locale.Tr "user.code"}}
</a> </a>

View File

@ -138,6 +138,12 @@
<label>admin:org_hook</label> <label>admin:org_hook</label>
</div> </div>
</div> </div>
<div class="field">
<div class="ui checkbox">
<input class="enable-system" type="checkbox" name="scope" value="admin:user_hook">
<label>admin:user_hook</label>
</div>
</div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<input class="enable-system" type="checkbox" name="scope" value="notification"> <input class="enable-system" type="checkbox" name="scope" value="notification">

View File

@ -0,0 +1,53 @@
{{template "base/head" .}}
<div class="page-content user settings new webhook">
{{template "user/settings/navbar" .}}
<div class="ui container">
<div class="twelve wide column content">
{{template "base/alert" .}}
<h4 class="ui top attached header">
{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}}
<div class="ui right">
{{if eq .HookType "gitea"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gitea.svg">
{{else if eq .HookType "gogs"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/gogs.ico">
{{else if eq .HookType "slack"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/slack.png">
{{else if eq .HookType "discord"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/discord.png">
{{else if eq .HookType "dingtalk"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/dingtalk.ico">
{{else if eq .HookType "telegram"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/telegram.png">
{{else if eq .HookType "msteams"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/msteams.png">
{{else if eq .HookType "feishu"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/feishu.png">
{{else if eq .HookType "matrix"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/matrix.svg">
{{else if eq .HookType "wechatwork"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/wechatwork.png">
{{else if eq .HookType "packagist"}}
<img width="26" height="26" src="{{AssetUrlPrefix}}/img/packagist.png">
{{end}}
</div>
</h4>
<div class="ui attached segment">
{{template "repo/settings/webhook/gitea" .}}
{{template "repo/settings/webhook/gogs" .}}
{{template "repo/settings/webhook/slack" .}}
{{template "repo/settings/webhook/discord" .}}
{{template "repo/settings/webhook/dingtalk" .}}
{{template "repo/settings/webhook/telegram" .}}
{{template "repo/settings/webhook/msteams" .}}
{{template "repo/settings/webhook/feishu" .}}
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
{{template "repo/settings/webhook/packagist" .}}
</div>
{{template "repo/settings/webhook/history" .}}
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -0,0 +1,8 @@
{{template "base/head" .}}
<div class="page-content user settings webhooks">
{{template "user/settings/navbar" .}}
<div class="ui container">
{{template "repo/settings/webhook/list" .}}
</div>
</div>
{{template "base/footer" .}}

View File

@ -26,6 +26,11 @@
{{.locale.Tr "packages.title"}} {{.locale.Tr "packages.title"}}
</a> </a>
{{end}} {{end}}
{{if not DisableWebhooks}}
<a class="{{if .PageIsSettingsHooks}}active {{end}}item" href="{{AppSubUrl}}/user/settings/hooks">
{{.locale.Tr "repo.settings.hooks"}}
</a>
{{end}}
<a class="{{if .PageIsSettingsOrganization}}active {{end}}item" href="{{AppSubUrl}}/user/settings/organization"> <a class="{{if .PageIsSettingsOrganization}}active {{end}}item" href="{{AppSubUrl}}/user/settings/organization">
{{.locale.Tr "settings.organization"}} {{.locale.Tr "settings.organization"}}
</a> </a>

View File

@ -55,28 +55,6 @@ LFS_START_SERVER = true
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE=
[lfs]
MINIO_BASE_PATH = lfs/
[attachment]
MINIO_BASE_PATH = attachments/
[avatars]
MINIO_BASE_PATH = avatars/
[repo-avatars]
MINIO_BASE_PATH = repo-avatars/
[storage]
STORAGE_TYPE = minio
SERVE_DIRECT = false
MINIO_ENDPOINT = minio:9000
MINIO_ACCESS_KEY_ID = 123456
MINIO_SECRET_ACCESS_KEY = 12345678
MINIO_BUCKET = gitea
MINIO_LOCATION = us-east-1
MINIO_USE_SSL = false
[mailer] [mailer]
ENABLED = true ENABLED = true
MAILER_TYPE = dummy MAILER_TYPE = dummy
@ -122,6 +100,9 @@ INSTALL_LOCK = true
SECRET_KEY = 9pCviYTWSb SECRET_KEY = 9pCviYTWSb
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ
[lfs]
PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/data/lfs
[packages] [packages]
ENABLED = true ENABLED = true

View File

@ -105,7 +105,26 @@ SECRET_KEY = 9pCviYTWSb
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ
[lfs] [lfs]
PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/lfs MINIO_BASE_PATH = lfs/
[attachment]
MINIO_BASE_PATH = attachments/
[avatars]
MINIO_BASE_PATH = avatars/
[repo-avatars]
MINIO_BASE_PATH = repo-avatars/
[storage]
STORAGE_TYPE = minio
SERVE_DIRECT = false
MINIO_ENDPOINT = minio:9000
MINIO_ACCESS_KEY_ID = 123456
MINIO_SECRET_ACCESS_KEY = 12345678
MINIO_BUCKET = gitea
MINIO_LOCATION = us-east-1
MINIO_USE_SSL = false
[packages] [packages]
ENABLED = true ENABLED = true

View File

@ -3,7 +3,7 @@ import {attachTribute} from './tribute.js';
import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js'; import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
import {initEasyMDEImagePaste} from './comp/ImagePaste.js'; import {initEasyMDEImagePaste} from './comp/ImagePaste.js';
import {createCommentEasyMDE} from './comp/EasyMDE.js'; import {createCommentEasyMDE} from './comp/EasyMDE.js';
import {hideElem} from '../utils/dom.js'; import {hideElem, showElem} from '../utils/dom.js';
export function initRepoRelease() { export function initRepoRelease() {
$(document).on('click', '.remove-rel-attach', function() { $(document).on('click', '.remove-rel-attach', function() {
@ -14,8 +14,43 @@ export function initRepoRelease() {
}); });
} }
export function initRepoReleaseNew() {
const $repoReleaseNew = $('.repository.new.release');
if (!$repoReleaseNew.length) return;
export function initRepoReleaseEditor() { initTagNameEditor();
initRepoReleaseEditor();
}
function initTagNameEditor() {
const el = document.getElementById('tag-name-editor');
if (!el) return;
const existingTags = JSON.parse(el.getAttribute('data-existing-tags'));
if (!Array.isArray(existingTags)) return;
const defaultTagHelperText = el.getAttribute('data-tag-helper');
const newTagHelperText = el.getAttribute('data-tag-helper-new');
const existingTagHelperText = el.getAttribute('data-tag-helper-existing');
document.getElementById('tag-name').addEventListener('keyup', (e) => {
const value = e.target.value;
if (existingTags.includes(value)) {
// If the tag already exists, hide the target branch selector.
hideElem('#tag-target-selector');
document.getElementById('tag-helper').innerText = existingTagHelperText;
} else {
showElem('#tag-target-selector');
if (value) {
document.getElementById('tag-helper').innerText = newTagHelperText;
} else {
document.getElementById('tag-helper').innerText = defaultTagHelperText;
}
}
});
}
function initRepoReleaseEditor() {
const $editor = $('.repository.new.release .content-editor'); const $editor = $('.repository.new.release .content-editor');
if ($editor.length === 0) { if ($editor.length === 0) {
return; return;

View File

@ -76,7 +76,7 @@ import {
import {initViewedCheckboxListenerFor} from './features/pull-view-file.js'; import {initViewedCheckboxListenerFor} from './features/pull-view-file.js';
import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.js'; import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.js';
import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.js'; import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.js';
import {initRepoRelease, initRepoReleaseEditor} from './features/repo-release.js'; import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.js';
import {initRepoEditor} from './features/repo-editor.js'; import {initRepoEditor} from './features/repo-editor.js';
import {initCompSearchUserBox} from './features/comp/SearchUserBox.js'; import {initCompSearchUserBox} from './features/comp/SearchUserBox.js';
import {initInstall} from './features/install.js'; import {initInstall} from './features/install.js';
@ -179,7 +179,7 @@ $(document).ready(() => {
initRepoPullRequestAllowMaintainerEdit(); initRepoPullRequestAllowMaintainerEdit();
initRepoPullRequestReview(); initRepoPullRequestReview();
initRepoRelease(); initRepoRelease();
initRepoReleaseEditor(); initRepoReleaseNew();
initRepoSettingGitHook(); initRepoSettingGitHook();
initRepoSettingSearchTeamBox(); initRepoSettingSearchTeamBox();
initRepoSettingsCollaboration(); initRepoSettingsCollaboration();