Template
1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo synced 2024-11-29 05:06:11 +01:00

Move macaron to chi (#14293)

Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR.

- [x] Define `context.ResponseWriter` interface with an implementation `context.Response`.
- [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before.
- [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic .
- [x] Use https://github.com/unrolled/render instead of macaron's internal render
- [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip
- [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK**
- [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha
- [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache
- [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding
- [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors
- [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation`
- [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle.
- [x] Removed macaron log service because it's not need any more. **BREAK**
- [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition.
- [x] Move Git HTTP protocol implementation to use routers directly.
- [x] Fix the problem that chi routes don't support trailing slash but macaron did.
- [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. 

Notices:
- Chi router don't support request with trailing slash
- Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI.

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Lunny Xiao 2021-01-26 23:36:53 +08:00 committed by GitHub
parent 3adbbb4255
commit 6433ba0ec3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
353 changed files with 5463 additions and 20785 deletions

View file

@ -70,7 +70,7 @@ issues:
- path: modules/log/ - path: modules/log/
linters: linters:
- errcheck - errcheck
- path: routers/routes/macaron.go - path: routers/routes/web.go
linters: linters:
- dupl - dupl
- path: routers/api/v1/repo/issue_subscription.go - path: routers/api/v1/repo/issue_subscription.go

View file

@ -21,7 +21,7 @@ import (
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/macaron/session" "gitea.com/go-chi/session"
archiver "github.com/mholt/archiver/v3" archiver "github.com/mholt/archiver/v3"
"github.com/urfave/cli" "github.com/urfave/cli"
) )

View file

@ -102,8 +102,7 @@ func runWeb(ctx *cli.Context) error {
return err return err
} }
} }
c := routes.NewChi() c := routes.InstallRoutes()
routes.RegisterInstallRoute(c)
err := listen(c, false) err := listen(c, false)
select { select {
case <-graceful.GetManager().IsShutdown(): case <-graceful.GetManager().IsShutdown():
@ -134,11 +133,9 @@ func runWeb(ctx *cli.Context) error {
return err return err
} }
} }
// Set up Chi routes
c := routes.NewChi()
c.Mount("/", routes.NormalRoutes())
routes.DelegateToMacaron(c)
// Set up Chi routes
c := routes.NormalRoutes()
err := listen(c, true) err := listen(c, true)
<-graceful.GetManager().Done() <-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid()) log.Info("PID: %d Gitea Web Finished", os.Getpid())

View file

@ -116,9 +116,7 @@ func runPR() {
//routers.GlobalInit() //routers.GlobalInit()
external.RegisterParsers() external.RegisterParsers()
markup.Init() markup.Init()
c := routes.NewChi() c := routes.NormalRoutes()
c.Mount("/", routes.NormalRoutes())
routes.DelegateToMacaron(c)
log.Printf("[PR] Ready for testing !\n") log.Printf("[PR] Ready for testing !\n")
log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n") log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n")

View file

@ -549,7 +549,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
## Session (`session`) ## Session (`session`)
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, nodb, postgres\]. - `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, postgres\].
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for others, the connection string. - `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for others, the connection string.
- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access. - `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID. - `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
@ -609,8 +609,6 @@ Default templates for project boards:
- `MODE`: **console**: Logging mode. For multiple modes, use a comma to separate values. You can configure each mode in per mode log subsections `\[log.modename\]`. By default the file mode will log to `$ROOT_PATH/gitea.log`. - `MODE`: **console**: Logging mode. For multiple modes, use a comma to separate values. You can configure each mode in per mode log subsections `\[log.modename\]`. By default the file mode will log to `$ROOT_PATH/gitea.log`.
- `LEVEL`: **Info**: General log level. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\] - `LEVEL`: **Info**: General log level. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\]
- `STACKTRACE_LEVEL`: **None**: Default log level at which to log create stack traces. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\] - `STACKTRACE_LEVEL`: **None**: Default log level at which to log create stack traces. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\]
- `REDIRECT_MACARON_LOG`: **false**: Redirects the Macaron log to its own logger or the default logger.
- `MACARON`: **file**: Logging mode for the macaron logger, use a comma to separate values. Configure each mode in per mode log subsections `\[log.modename.macaron\]`. By default the file mode will log to `$ROOT_PATH/macaron.log`. (If you set this to `,` it will log to default gitea logger.)
- `ROUTER_LOG_LEVEL`: **Info**: The log level that the router should log at. (If you are setting the access log, its recommended to place this at Debug.) - `ROUTER_LOG_LEVEL`: **Info**: The log level that the router should log at. (If you are setting the access log, its recommended to place this at Debug.)
- `ROUTER`: **console**: The mode or name of the log the router should log to. (If you set this to `,` it will log to default gitea logger.) - `ROUTER`: **console**: The mode or name of the log the router should log to. (If you set this to `,` it will log to default gitea logger.)
NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` for this option to take effect. Configure each mode in per mode log subsections `\[log.modename.router\]`. NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` for this option to take effect. Configure each mode in per mode log subsections `\[log.modename.router\]`.
@ -618,7 +616,7 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false`
- `ACCESS`: **file**: Logging mode for the access logger, use a comma to separate values. Configure each mode in per mode log subsections `\[log.modename.access\]`. By default the file mode will log to `$ROOT_PATH/access.log`. (If you set this to `,` it will log to the default gitea logger.) - `ACCESS`: **file**: Logging mode for the access logger, use a comma to separate values. Configure each mode in per mode log subsections `\[log.modename.access\]`. By default the file mode will log to `$ROOT_PATH/access.log`. (If you set this to `,` it will log to the default gitea logger.)
- `ACCESS_LOG_TEMPLATE`: **`{{.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}}"`**: Sets the template used to create the access log. - `ACCESS_LOG_TEMPLATE`: **`{{.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}}"`**: Sets the template used to create the access log.
- The following variables are available: - The following variables are available:
- `Ctx`: the `macaron.Context` of the request. - `Ctx`: the `context.Context` of the request.
- `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.

View file

@ -67,40 +67,11 @@ The provider type of the sublogger can be set using the `MODE` value in
its subsection, but will default to the name. This allows you to have its subsection, but will default to the name. This allows you to have
multiple subloggers that will log to files. multiple subloggers that will log to files.
### The "Macaron" logger
By default Macaron will log to its own go `log` instance. This writes
to `os.Stdout`. You can redirect this log to a Gitea configurable logger
through setting the `REDIRECT_MACARON_LOG` setting in the `[log]`
section which you can configure the outputs of by setting the `MACARON`
value in the `[log]` section of the configuration. `MACARON` defaults
to `file` if unset.
Please note, the macaron logger will log at `INFO` level, setting the
`LEVEL` of this logger to `WARN` or above will result in no macaron logs.
Each output sublogger for this logger is configured in
`[log.sublogger.macaron]` sections. There are certain default values
which will not be inherited from the `[log]` or relevant
`[log.sublogger]` sections:
- `FLAGS` is `stdflags` (Equal to
`date,time,medfile,shortfuncname,levelinitial`)
- `FILE_NAME` will default to `%(ROOT_PATH)/macaron.log`
- `EXPRESSION` will default to `""`
- `PREFIX` will default to `""`
NB: You can redirect the macaron logger to send its events to the gitea
log using the value: `MACARON = ,`
### The "Router" logger ### The "Router" logger
There are two types of Router log. By default Macaron send its own You can disable Router log by setting `DISABLE_ROUTER_LOG`.
router log which will be directed to Macaron's go `log`, however if you
`REDIRECT_MACARON_LOG` you will enable Gitea's router log. You can
disable both types of Router log by setting `DISABLE_ROUTER_LOG`.
If you enable the redirect, you can configure the outputs of this You can configure the outputs of this
router log by setting the `ROUTER` value in the `[log]` section of the router log by setting the `ROUTER` value in the `[log]` section of the
configuration. `ROUTER` will default to `console` if unset. The Gitea configuration. `ROUTER` will default to `console` if unset. The Gitea
Router logs the same data as the Macaron log but has slightly different Router logs the same data as the Macaron log but has slightly different
@ -162,11 +133,11 @@ This value represent a go template. It's default value is:
The template is passed following options: The template is passed following options:
- `Ctx` is the `macaron.Context` - `Ctx` is the `context.Context`
- `Identity` is the `SignedUserName` or `"-"` if the user is not logged - `Identity` is the `SignedUserName` or `"-"` if the user is not logged
in in
- `Start` is the start time of the request - `Start` is the start time of the request
- `ResponseWriter` is the `macaron.ResponseWriter` - `ResponseWriter` is the `http.ResponseWriter`
Caution must be taken when changing this template as it runs outside of Caution must be taken when changing this template as it runs outside of
the standard panic recovery trap. The template should also be as simple the standard panic recovery trap. The template should also be as simple

View file

@ -267,7 +267,7 @@ Windows, on architectures like amd64, i386, ARM, PowerPC, and others.
## Components ## Components
* Web framework: [Macaron](http://go-macaron.com/) * Web framework: [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io) * ORM: [XORM](https://xorm.io)
* UI components: * UI components:
* [Semantic UI](http://semantic-ui.com/) * [Semantic UI](http://semantic-ui.com/)

View file

@ -254,7 +254,7 @@ Le but de ce projet est de fournir de la manière la plus simple, la plus rapide
## Composants ## Composants
* Framework web : [Macaron](http://go-macaron.com/) * Framework web : [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io) * ORM: [XORM](https://xorm.io)
* Interface graphique : * Interface graphique :
* [Semantic UI](http://semantic-ui.com/) * [Semantic UI](http://semantic-ui.com/)

View file

@ -47,7 +47,7 @@ Gitea的首要目标是创建一个极易安装运行非常快速安装和
## 组件 ## 组件
* Web框架 [Macaron](http://go-macaron.com/) * Web框架 [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io) * ORM: [XORM](https://xorm.io)
* UI组件 * UI组件
* [Semantic UI](http://semantic-ui.com/) * [Semantic UI](http://semantic-ui.com/)

View file

@ -47,7 +47,7 @@ Gitea 的首要目標是建立一個容易安裝,運行快速,安装和使
## 元件 ## 元件
* Web 框架: [Macaron](http://go-macaron.com/) * Web 框架: [Chi](http://github.com/go-chi/chi)
* ORM [XORM](https://xorm.io) * ORM [XORM](https://xorm.io)
* UI 元件: * UI 元件:
* [Semantic UI](http://semantic-ui.com/) * [Semantic UI](http://semantic-ui.com/)

16
go.mod
View file

@ -5,19 +5,12 @@ go 1.14
require ( require (
code.gitea.io/gitea-vet v0.2.1 code.gitea.io/gitea-vet v0.2.1
code.gitea.io/sdk/gitea v0.13.1 code.gitea.io/sdk/gitea v0.13.1
gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee
gitea.com/lunny/levelqueue v0.3.0 gitea.com/lunny/levelqueue v0.3.0
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b github.com/NYTimes/gziphandler v1.1.1
gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b
gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca
gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4
gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439
gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5
gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a
gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804
gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
github.com/PuerkitoBio/goquery v1.5.1 github.com/PuerkitoBio/goquery v1.5.1
github.com/RoaringBitmap/roaring v0.5.5 // indirect github.com/RoaringBitmap/roaring v0.5.5 // indirect
github.com/alecthomas/chroma v0.8.2 github.com/alecthomas/chroma v0.8.2
@ -36,6 +29,7 @@ require (
github.com/gliderlabs/ssh v0.3.1 github.com/gliderlabs/ssh v0.3.1
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
github.com/go-chi/chi v1.5.1 github.com/go-chi/chi v1.5.1
github.com/go-chi/cors v1.1.1
github.com/go-enry/go-enry/v2 v2.6.0 github.com/go-enry/go-enry/v2 v2.6.0
github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.2.0 github.com/go-git/go-git/v5 v5.2.0

60
go.sum
View file

@ -40,44 +40,16 @@ code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFj
code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk= code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk=
code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c h1:NTtrGYjR40WUdkCdn26Y5LGFT52rIkFPkjmtgCAyiTs=
gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c/go.mod h1:9bGA9dIsrz+wVQKH1DzvxuAvrudHaQ8Wx8hLme/GVGQ=
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e h1:zgPGaf3kXP0cVm9J0l8ZA2+XDzILYATg0CXbihR6N+o=
gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e h1:YjaQU6XFicdhPN+MlGolcXO8seYY2+EY5g7vZPB17CQ=
gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e/go.mod h1:nfA7JaGv3hbGQ1ktdhAsZhdS84qKffI8NMlHr+Opsog=
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee h1:9U6HuKUBt/cGK6T/64dEuz0r7Yp97WAAEJvXHDlY3ws= gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee h1:9U6HuKUBt/cGK6T/64dEuz0r7Yp97WAAEJvXHDlY3ws=
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0= gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0=
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I= gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I=
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727 h1:ZF2Bd6rqVlwhIDhYiS0uGYcT+GaVNGjuKVJkTNqWMIs=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ=
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b/go.mod h1:Cxadig6POWpPYYSfg23E7jo35Yf0yvsdC1lifoKWmPo=
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 h1:mMsMEg90c5KXQgRWsH8D6GHXfZIW1RAe5S9VYIb12lM=
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76/go.mod h1:NFHb9Of+LUnU86bU20CiXXg6ZlgCJ4XytP14UsHOXFs=
gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b h1:2ZE0JE3bKVBcP1VTrWeE1jqWwCAMIzfOQm1U9EGbBKU=
gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b/go.mod h1:W5hKG8T1GBfypp5CRQlgoJU4figIL0jhx02y4XA/NOA=
gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca h1:f5P41nXmXd/YOh8f6098Q0F1Y0QfpyRPSSIkni2XH4Q=
gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca/go.mod h1:J5h3N+1nKTXtU1x4GxexaQKgAz8UiWecNwi/CfX7CtQ=
gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 h1:e2rAFDejB0qN8OrY4xP4XSu8/yT6QmWxDZpB3J7r2GU=
gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4/go.mod h1:rtOK4J20kpMD9XcNsnO5YA843YSTe/MUMbDj/TJ/Q7A=
gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 h1:88c34YM29a1GlWLrLBaG/GTT2htDdJz1u3n9+lmPolg=
gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439/go.mod h1:IsQPHx73HnnqFBYiVHjg87q4XBZyGXXu77xANukvZuk=
gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5 h1:6rbhThlqfOb+sSmhrsVFz3bZoAeoloe7TZqyeiPbbWI=
gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5/go.mod h1:z8vCjuhqDfvzPUJDowGqbsgoeYBvDbl95S5k6y43Pxo=
gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60 h1:tNWNe5HBIlsfapFMtT4twTbXQmInRQWmdWNi8Di1ct0=
gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60/go.mod h1:g5ope1b+iWhBdHzAn6EJ9u9Gp3FRESxpG+CDf7HYc/A=
gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok=
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw=
gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
gitea.com/macaron/macaron v1.5.0 h1:TvWEcHw1/zaHlo0GTuKEukLh3A99+QsU2mjBrXLXjVQ=
gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804 h1:yUiJVZKzdXsBe2tumTAXHBZa1qPGoGXM3fBG4RJ5fQg=
gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA=
gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee h1:8/N3a56RXRJ66nnep0z+T7oHCB0bY6lpvtjv9Y9FPhE=
gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee/go.mod h1:5tJCkDbrwpGv+MQUSIZSOW0wFrkh0exsonJgOvBs1Dw=
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14mefLzGluqV7w2mGU3u+iZU+jCeWk=
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/6543-forks/go-gogs-client v0.0.0-20210116182316-f2f8bc0ea9cc h1:FLylYVXDwK+YtrmXYnv2Q1Y5lQ9TU1Xp5F2vndIOTb4= github.com/6543-forks/go-gogs-client v0.0.0-20210116182316-f2f8bc0ea9cc h1:FLylYVXDwK+YtrmXYnv2Q1Y5lQ9TU1Xp5F2vndIOTb4=
@ -92,6 +64,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
@ -106,7 +80,6 @@ github.com/RoaringBitmap/roaring v0.5.5 h1:naNqvO1mNnghk2UvcsqnzHDBn9DRbCIRy94Gm
github.com/RoaringBitmap/roaring v0.5.5/go.mod h1:puNo5VdzwbaIQxSiDIwfXl4Hnc+fbovcX4IW/dSTtUk= github.com/RoaringBitmap/roaring v0.5.5/go.mod h1:puNo5VdzwbaIQxSiDIwfXl4Hnc+fbovcX4IW/dSTtUk=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
@ -233,19 +206,13 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs= github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs=
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/gomemcached v0.1.0 h1:whUde87n8CScx8ckMp2En5liqAlcuG3aKy/BQeBPu84=
github.com/couchbase/gomemcached v0.1.0/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/gomemcached v0.1.1 h1:xCS8ZglJDhrlQg3jmK7Rn1V8f7bPjXABLC05CgLQauc= github.com/couchbase/gomemcached v0.1.1 h1:xCS8ZglJDhrlQg3jmK7Rn1V8f7bPjXABLC05CgLQauc=
github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4= github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4=
github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw= github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4= github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7 h1:1XjEY/gnjQ+AfXef2U6dxCquhiRzkEpxZuWqs+QxTL8=
github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@ -331,6 +298,8 @@ github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w= github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-enry/go-enry/v2 v2.6.0 h1:nbGWQBpO+D+cJuRxNgSDFnFY9QWz3QM/CeZxU7VAH20= github.com/go-enry/go-enry/v2 v2.6.0 h1:nbGWQBpO+D+cJuRxNgSDFnFY9QWz3QM/CeZxU7VAH20=
github.com/go-enry/go-enry/v2 v2.6.0/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ= github.com/go-enry/go-enry/v2 v2.6.0/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
@ -548,7 +517,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
@ -700,7 +668,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc= github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
@ -995,7 +962,6 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
@ -1068,7 +1034,6 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0YYjZvDMJPYN7W9FGSGNoLmKxM= github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0YYjZvDMJPYN7W9FGSGNoLmKxM=
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs= github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs=
@ -1171,9 +1136,6 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1535,12 +1497,10 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View file

@ -14,7 +14,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"

View file

@ -10,7 +10,7 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"

View file

@ -131,7 +131,7 @@ func TestAPIGetReleaseByTag(t *testing.T) {
tag := "v1.1" tag := "v1.1"
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s/", urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s",
owner.Name, repo.Name, tag) owner.Name, repo.Name, tag)
req := NewRequestf(t, "GET", urlStr) req := NewRequestf(t, "GET", urlStr)
@ -144,7 +144,7 @@ func TestAPIGetReleaseByTag(t *testing.T) {
nonexistingtag := "nonexistingtag" nonexistingtag := "nonexistingtag"
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s/", urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s",
owner.Name, repo.Name, nonexistingtag) owner.Name, repo.Name, nonexistingtag)
req = NewRequestf(t, "GET", urlStr) req = NewRequestf(t, "GET", urlStr)
@ -163,7 +163,7 @@ func TestAPIDeleteTagByName(t *testing.T) {
session := loginUser(t, owner.LowerName) session := loginUser(t, owner.LowerName)
token := getTokenForLoggedInUser(t, session) token := getTokenForLoggedInUser(t, session)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/delete-tag/?token=%s", urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/delete-tag?token=%s",
owner.Name, repo.Name, token) owner.Name, repo.Name, token)
req := NewRequestf(t, http.MethodDelete, urlStr) req := NewRequestf(t, http.MethodDelete, urlStr)
@ -171,7 +171,7 @@ func TestAPIDeleteTagByName(t *testing.T) {
// Make sure that actual releases can't be deleted outright // Make sure that actual releases can't be deleted outright
createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test") createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag/?token=%s", urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s",
owner.Name, repo.Name, token) owner.Name, repo.Name, token)
req = NewRequestf(t, http.MethodDelete, urlStr) req = NewRequestf(t, http.MethodDelete, urlStr)

View file

@ -17,7 +17,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/routes" "code.gitea.io/gitea/routers/routes"
"gitea.com/macaron/session" "gitea.com/go-chi/session"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -58,9 +58,7 @@ func TestSessionFileCreation(t *testing.T) {
oldSessionConfig := setting.SessionConfig.ProviderConfig oldSessionConfig := setting.SessionConfig.ProviderConfig
defer func() { defer func() {
setting.SessionConfig.ProviderConfig = oldSessionConfig setting.SessionConfig.ProviderConfig = oldSessionConfig
c = routes.NewChi() c = routes.NormalRoutes()
c.Mount("/", routes.NormalRoutes())
routes.DelegateToMacaron(c)
}() }()
var config session.Options var config session.Options
@ -84,9 +82,7 @@ func TestSessionFileCreation(t *testing.T) {
setting.SessionConfig.ProviderConfig = string(newConfigBytes) setting.SessionConfig.ProviderConfig = string(newConfigBytes)
c = routes.NewChi() c = routes.NormalRoutes()
c.Mount("/", routes.NormalRoutes())
routes.DelegateToMacaron(c)
t.Run("NoSessionOnViewIssue", func(t *testing.T) { t.Run("NoSessionOnViewIssue", func(t *testing.T) {
defer PrintCurrentTest(t)() defer PrintCurrentTest(t)()

View file

@ -31,15 +31,15 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes" "code.gitea.io/gitea/routers/routes"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/go-chi/chi"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var c chi.Router var c *web.Route
type NilResponseRecorder struct { type NilResponseRecorder struct {
httptest.ResponseRecorder httptest.ResponseRecorder
@ -66,9 +66,7 @@ func TestMain(m *testing.M) {
defer cancel() defer cancel()
initIntegrationTest() initIntegrationTest()
c = routes.NewChi() c = routes.NormalRoutes()
c.Mount("/", routes.NormalRoutes())
routes.DelegateToMacaron(c)
// integration test settings... // integration test settings...
if setting.Cfg != nil { if setting.Cfg != nil {
@ -387,6 +385,9 @@ func NewRequestWithJSON(t testing.TB, method, urlStr string, v interface{}) *htt
func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *http.Request { func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *http.Request {
t.Helper() t.Helper()
if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") {
urlStr = "/" + urlStr
}
request, err := http.NewRequest(method, urlStr, body) request, err := http.NewRequest(method, urlStr, body)
assert.NoError(t, err) assert.NoError(t, err)
request.RequestURI = urlStr request.RequestURI = urlStr

View file

@ -19,8 +19,8 @@ import (
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/routers/routes"
"gitea.com/macaron/gzip"
gzipp "github.com/klauspost/compress/gzip" gzipp "github.com/klauspost/compress/gzip"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -121,7 +121,7 @@ func TestGetLFSLarge(t *testing.T) {
t.Skip() t.Skip()
return return
} }
content := make([]byte, gzip.MinSize*10) content := make([]byte, routes.GzipMinSize*10)
for i := range content { for i := range content {
content[i] = byte(i % 256) content[i] = byte(i % 256)
} }
@ -137,7 +137,7 @@ func TestGetLFSGzip(t *testing.T) {
t.Skip() t.Skip()
return return
} }
b := make([]byte, gzip.MinSize*10) b := make([]byte, routes.GzipMinSize*10)
for i := range b { for i := range b {
b[i] = byte(i % 256) b[i] = byte(i % 256)
} }
@ -158,7 +158,7 @@ func TestGetLFSZip(t *testing.T) {
t.Skip() t.Skip()
return return
} }
b := make([]byte, gzip.MinSize*10) b := make([]byte, routes.GzipMinSize*10)
for i := range b { for i := range b {
b[i] = byte(i % 256) b[i] = byte(i % 256)
} }

View file

@ -32,7 +32,6 @@ func TestLinksNoLogin(t *testing.T) {
"/user/login", "/user/login",
"/user/forgot_password", "/user/forgot_password",
"/api/swagger", "/api/swagger",
"/api/v1/swagger",
"/user2/repo1", "/user2/repo1",
"/user2/repo1/projects", "/user2/repo1/projects",
"/user2/repo1/projects/1", "/user2/repo1/projects/1",
@ -53,6 +52,7 @@ func TestRedirectsNoLogin(t *testing.T) {
"/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt", "/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt",
"/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt", "/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt",
"/user/avatar/Ghost/-1": "/img/avatar_default.png", "/user/avatar/Ghost/-1": "/img/avatar_default.png",
"/api/v1/swagger": "/api/swagger",
} }
for link, redirectLink := range redirects { for link, redirectLink := range redirects {
req := NewRequest(t, "GET", link) req := NewRequest(t, "GET", link)
@ -86,7 +86,6 @@ func testLinksAsUser(userName string, t *testing.T) {
"/", "/",
"/user/forgot_password", "/user/forgot_password",
"/api/swagger", "/api/swagger",
"/api/v1/swagger",
"/issues", "/issues",
"/issues?type=your_repositories&repos=[0]&sort=&state=open", "/issues?type=your_repositories&repos=[0]&sort=&state=open",
"/issues?type=assigned&repos=[0]&sort=&state=open", "/issues?type=assigned&repos=[0]&sort=&state=open",

View file

@ -8,19 +8,15 @@ import (
"net/http" "net/http"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/session"
) )
// DataStore represents a data store // DataStore represents a data store
type DataStore interface { type DataStore middlewares.DataStore
GetData() map[string]interface{}
}
// SessionStore represents a session store // SessionStore represents a session store
type SessionStore interface { type SessionStore session.Store
Get(interface{}) interface{}
Set(interface{}, interface{}) error
Delete(interface{}) error
}
// SingleSignOn represents a SSO authentication method (plugin) for HTTP requests. // SingleSignOn represents a SSO authentication method (plugin) for HTTP requests.
type SingleSignOn interface { type SingleSignOn interface {

View file

@ -62,6 +62,8 @@ func (o *OAuth2) Free() error {
// userIDFromToken returns the user id corresponding to the OAuth token. // userIDFromToken returns the user id corresponding to the OAuth token.
func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 { func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 {
_ = req.ParseForm()
// Check access token. // Check access token.
tokenSHA := req.Form.Get("token") tokenSHA := req.Form.Get("token")
if len(tokenSHA) == 0 { if len(tokenSHA) == 0 {

View file

@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
mc "gitea.com/macaron/cache" mc "gitea.com/go-chi/cache"
_ "gitea.com/macaron/cache/memcache" // memcache plugin for cache _ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache
) )
var ( var (
@ -20,7 +20,7 @@ var (
) )
func newCache(cacheConfig setting.Cache) (mc.Cache, error) { func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
return mc.NewCacher(cacheConfig.Adapter, mc.Options{ return mc.NewCacher(mc.Options{
Adapter: cacheConfig.Adapter, Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn, AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval, Interval: cacheConfig.Interval,

View file

@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/modules/nosql" "code.gitea.io/gitea/modules/nosql"
"gitea.com/macaron/cache" "gitea.com/go-chi/cache"
"github.com/go-redis/redis/v7" "github.com/go-redis/redis/v7"
"github.com/unknwon/com" "github.com/unknwon/com"
) )

View file

@ -6,18 +6,21 @@
package context package context
import ( import (
"context"
"fmt" "fmt"
"html"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/csrf" "gitea.com/go-chi/session"
"gitea.com/macaron/macaron"
) )
// APIContext is a specific macaron context for API service // APIContext is a specific macaron context for API service
@ -91,7 +94,7 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
if status == http.StatusInternalServerError { if status == http.StatusInternalServerError {
log.ErrorWithSkip(1, "%s: %s", title, message) log.ErrorWithSkip(1, "%s: %s", title, message)
if macaron.Env == macaron.PROD && !(ctx.User != nil && ctx.User.IsAdmin) { if setting.IsProd() && !(ctx.User != nil && ctx.User.IsAdmin) {
message = "" message = ""
} }
} }
@ -108,7 +111,7 @@ func (ctx *APIContext) InternalServerError(err error) {
log.ErrorWithSkip(1, "InternalServerError: %v", err) log.ErrorWithSkip(1, "InternalServerError: %v", err)
var message string var message string
if macaron.Env != macaron.PROD || (ctx.User != nil && ctx.User.IsAdmin) { if !setting.IsProd() || (ctx.User != nil && ctx.User.IsAdmin) {
message = err.Error() message = err.Error()
} }
@ -118,6 +121,20 @@ func (ctx *APIContext) InternalServerError(err error) {
}) })
} }
var (
apiContextKey interface{} = "default_api_context"
)
// WithAPIContext set up api context in request
func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx))
}
// GetAPIContext returns a context for API routes
func GetAPIContext(req *http.Request) *APIContext {
return req.Context().Value(apiContextKey).(*APIContext)
}
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
page := NewPagination(total, pageSize, curPage, 0) page := NewPagination(total, pageSize, curPage, 0)
paginater := page.Paginater paginater := page.Paginater
@ -172,7 +189,7 @@ func (ctx *APIContext) RequireCSRF() {
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName()) headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName()) formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
if len(headerToken) > 0 || len(formValueToken) > 0 { if len(headerToken) > 0 || len(formValueToken) > 0 {
csrf.Validate(ctx.Context.Context, ctx.csrf) Validate(ctx.Context, ctx.csrf)
} else { } else {
ctx.Context.Error(401, "Missing CSRF token.") ctx.Context.Error(401, "Missing CSRF token.")
} }
@ -201,42 +218,91 @@ func (ctx *APIContext) CheckForOTP() {
} }
// APIContexter returns apicontext as macaron middleware // APIContexter returns apicontext as macaron middleware
func APIContexter() macaron.Handler { func APIContexter() func(http.Handler) http.Handler {
return func(c *Context) { var csrfOpts = getCsrfOpts()
ctx := &APIContext{
Context: c, return func(next http.Handler) http.Handler {
}
c.Map(ctx) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var locale = middlewares.Locale(w, req)
var ctx = APIContext{
Context: &Context{
Resp: NewResponse(w),
Data: map[string]interface{}{},
Locale: locale,
Session: session.GetSession(req),
Repo: &Repository{
PullRequest: &PullRequest{},
},
Org: &Organization{},
},
Org: &APIOrganization{},
}
ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
ctx.csrf = Csrfer(csrfOpts, ctx.Context)
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.InternalServerError(err)
return
}
}
// Get user from session if logged in.
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
if ctx.User != nil {
ctx.IsSigned = true
ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
} else {
ctx.Data["SignedUserID"] = int64(0)
ctx.Data["SignedUserName"] = ""
}
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
next.ServeHTTP(ctx.Resp, ctx.Req)
})
} }
} }
// ReferencesGitRepo injects the GitRepo into the Context // ReferencesGitRepo injects the GitRepo into the Context
func ReferencesGitRepo(allowEmpty bool) macaron.Handler { func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
return func(ctx *APIContext) { return func(next http.Handler) http.Handler {
// Empty repository does not have reference information. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if !allowEmpty && ctx.Repo.Repository.IsEmpty { ctx := GetAPIContext(req)
return // Empty repository does not have reference information.
} if !allowEmpty && ctx.Repo.Repository.IsEmpty {
// For API calls.
if ctx.Repo.GitRepo == nil {
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
ctx.Error(500, "RepoRef Invalid repo "+repoPath, err)
return return
} }
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
defer func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
}
ctx.Next() // For API calls.
if ctx.Repo.GitRepo == nil {
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
ctx.Error(500, "RepoRef Invalid repo "+repoPath, err)
return
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
defer func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
}
next.ServeHTTP(w, req)
})
} }
} }
@ -266,8 +332,9 @@ func (ctx *APIContext) NotFound(objs ...interface{}) {
} }
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given // RepoRefForAPI handles repository reference names when the ref name is not explicitly given
func RepoRefForAPI() macaron.Handler { func RepoRefForAPI(next http.Handler) http.Handler {
return func(ctx *APIContext) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
// Empty repository does not have reference information. // Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
return return
@ -319,6 +386,6 @@ func RepoRefForAPI() macaron.Handler {
return return
} }
ctx.Next() next.ServeHTTP(w, req)
} })
} }

View file

@ -7,12 +7,8 @@ package context
import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/csrf"
"gitea.com/macaron/macaron"
) )
// ToggleOptions contains required or check options // ToggleOptions contains required or check options
@ -24,42 +20,23 @@ type ToggleOptions struct {
} }
// Toggle returns toggle options as middleware // Toggle returns toggle options as middleware
func Toggle(options *ToggleOptions) macaron.Handler { func Toggle(options *ToggleOptions) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path)
// Check prohibit login users. // Check prohibit login users.
if ctx.IsSigned { if ctx.IsSigned {
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm { if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account") ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
if isAPIPath {
ctx.JSON(403, map[string]string{
"message": "This account is not activated.",
})
return
}
ctx.HTML(200, "user/auth/activate") ctx.HTML(200, "user/auth/activate")
return return
} else if !ctx.User.IsActive || ctx.User.ProhibitLogin { }
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr()) log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
if isAPIPath {
ctx.JSON(403, map[string]string{
"message": "This account is prohibited from signing in, please contact your site administrator.",
})
return
}
ctx.HTML(200, "user/auth/prohibit_login") ctx.HTML(200, "user/auth/prohibit_login")
return return
} }
if ctx.User.MustChangePassword { if ctx.User.MustChangePassword {
if isAPIPath {
ctx.JSON(403, map[string]string{
"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
})
return
}
if ctx.Req.URL.Path != "/user/settings/change_password" { if ctx.Req.URL.Path != "/user/settings/change_password" {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password") ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
@ -82,8 +59,8 @@ func Toggle(options *ToggleOptions) macaron.Handler {
return return
} }
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" && !auth.IsAPIPath(ctx.Req.URL.Path) { if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
csrf.Validate(ctx.Context, ctx.csrf) Validate(ctx, ctx.csrf)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -91,13 +68,6 @@ func Toggle(options *ToggleOptions) macaron.Handler {
if options.SignInRequired { if options.SignInRequired {
if !ctx.IsSigned { if !ctx.IsSigned {
// Restrict API calls with error message.
if isAPIPath {
ctx.JSON(403, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
}
if ctx.Req.URL.Path != "/user/events" { if ctx.Req.URL.Path != "/user/events" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL) ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
} }
@ -108,32 +78,10 @@ func Toggle(options *ToggleOptions) macaron.Handler {
ctx.HTML(200, "user/auth/activate") ctx.HTML(200, "user/auth/activate")
return return
} }
if ctx.IsSigned && isAPIPath && ctx.IsBasicAuth {
twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.Error(500)
return
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
ctx.Error(500)
return
}
if !ok {
ctx.JSON(403, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
}
}
} }
// Redirect to log in page if auto-signin info is provided and has not signed in. // Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !ctx.IsSigned && !isAPIPath && if !options.SignOutRequired && !ctx.IsSigned &&
len(ctx.GetCookie(setting.CookieUserName)) > 0 { len(ctx.GetCookie(setting.CookieUserName)) > 0 {
if ctx.Req.URL.Path != "/user/events" { if ctx.Req.URL.Path != "/user/events" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL) ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
@ -151,3 +99,86 @@ func Toggle(options *ToggleOptions) macaron.Handler {
} }
} }
} }
// ToggleAPI returns toggle options as middleware
func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
return func(ctx *APIContext) {
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.JSON(403, map[string]string{
"message": "This account is not activated.",
})
return
}
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.JSON(403, map[string]string{
"message": "This account is prohibited from signing in, please contact your site administrator.",
})
return
}
if ctx.User.MustChangePassword {
ctx.JSON(403, map[string]string{
"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
})
return
}
}
// Redirect to dashboard if user tries to visit any non-login page.
if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
ctx.Redirect(setting.AppSubURL + "/")
return
}
if options.SignInRequired {
if !ctx.IsSigned {
// Restrict API calls with error message.
ctx.JSON(403, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(200, "user/auth/activate")
return
}
if ctx.IsSigned && ctx.IsBasicAuth {
twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
ctx.InternalServerError(err)
return
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
ctx.InternalServerError(err)
return
}
if !ok {
ctx.JSON(403, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
}
}
}
if options.AdminRequired {
if !ctx.User.IsAdmin {
ctx.JSON(403, map[string]string{
"message": "You have no permission to request for this.",
})
return
}
ctx.Data["PageIsAdmin"] = true
}
}
}

View file

@ -0,0 +1,26 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"sync"
"code.gitea.io/gitea/modules/setting"
"gitea.com/go-chi/captcha"
)
var imageCaptchaOnce sync.Once
var cpt *captcha.Captcha
// GetImageCaptcha returns global image captcha
func GetImageCaptcha() *captcha.Captcha {
imageCaptchaOnce.Do(func() {
cpt = captcha.NewCaptcha(captcha.Options{
SubURL: setting.AppSubURL,
})
})
return cpt
}

View file

@ -6,37 +6,55 @@
package context package context
import ( import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"html" "html"
"html/template" "html/template"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/sso" "code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/macaron/cache" "gitea.com/go-chi/cache"
"gitea.com/macaron/csrf" "gitea.com/go-chi/session"
"gitea.com/macaron/i18n" "github.com/go-chi/chi"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
"github.com/unknwon/com" "github.com/unknwon/com"
"github.com/unknwon/i18n"
"github.com/unrolled/render"
"golang.org/x/crypto/pbkdf2"
) )
// Render represents a template render
type Render interface {
TemplateLookup(tmpl string) *template.Template
HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...render.HTMLOptions) error
}
// Context represents context of a request. // Context represents context of a request.
type Context struct { type Context struct {
*macaron.Context Resp ResponseWriter
Req *http.Request
Data map[string]interface{}
Render Render
translation.Locale
Cache cache.Cache Cache cache.Cache
csrf csrf.CSRF csrf CSRF
Flash *session.Flash Flash *middlewares.Flash
Session session.Store Session session.Store
Link string // current request URL Link string // current request URL
@ -163,13 +181,22 @@ func (ctx *Context) RedirectToFirst(location ...string) {
// HTML calls Context.HTML and converts template name to string. // HTML calls Context.HTML and converts template name to string.
func (ctx *Context) HTML(status int, name base.TplName) { func (ctx *Context) HTML(status int, name base.TplName) {
log.Debug("Template: %s", name) log.Debug("Template: %s", name)
ctx.Context.HTML(status, string(name)) if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil {
ctx.ServerError("Render failed", err)
}
}
// HTMLString render content to a string but not http.ResponseWriter
func (ctx *Context) HTMLString(name string, data interface{}) (string, error) {
var buf strings.Builder
err := ctx.Render.HTML(&buf, 200, string(name), data)
return buf.String(), err
} }
// RenderWithErr used for page has form validation but need to prompt error to users. // RenderWithErr used for page has form validation but need to prompt error to users.
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) { func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
if form != nil { if form != nil {
auth.AssignForm(form, ctx.Data) middlewares.AssignForm(form, ctx.Data)
} }
ctx.Flash.ErrorMsg = msg ctx.Flash.ErrorMsg = msg
ctx.Data["Flash"] = ctx.Flash ctx.Data["Flash"] = ctx.Flash
@ -184,7 +211,7 @@ func (ctx *Context) NotFound(title string, err error) {
func (ctx *Context) notFoundInternal(title string, err error) { func (ctx *Context) notFoundInternal(title string, err error) {
if err != nil { if err != nil {
log.ErrorWithSkip(2, "%s: %v", title, err) log.ErrorWithSkip(2, "%s: %v", title, err)
if macaron.Env != macaron.PROD { if !setting.IsProd() {
ctx.Data["ErrorMsg"] = err ctx.Data["ErrorMsg"] = err
} }
} }
@ -203,7 +230,7 @@ func (ctx *Context) ServerError(title string, err error) {
func (ctx *Context) serverErrorInternal(title string, err error) { func (ctx *Context) serverErrorInternal(title string, err error) {
if err != nil { if err != nil {
log.ErrorWithSkip(2, "%s: %v", title, err) log.ErrorWithSkip(2, "%s: %v", title, err)
if macaron.Env != macaron.PROD { if !setting.IsProd() {
ctx.Data["ErrorMsg"] = err ctx.Data["ErrorMsg"] = err
} }
} }
@ -224,6 +251,44 @@ func (ctx *Context) NotFoundOrServerError(title string, errck func(error) bool,
ctx.serverErrorInternal(title, err) ctx.serverErrorInternal(title, err)
} }
// Header returns a header
func (ctx *Context) Header() http.Header {
return ctx.Resp.Header()
}
// FIXME: We should differ Query and Form, currently we just use form as query
// Currently to be compatible with macaron, we keep it.
// Query returns request form as string with default
func (ctx *Context) Query(key string, defaults ...string) string {
return (*Forms)(ctx.Req).MustString(key, defaults...)
}
// QueryTrim returns request form as string with default and trimmed spaces
func (ctx *Context) QueryTrim(key string, defaults ...string) string {
return (*Forms)(ctx.Req).MustTrimmed(key, defaults...)
}
// QueryStrings returns request form as strings with default
func (ctx *Context) QueryStrings(key string, defaults ...[]string) []string {
return (*Forms)(ctx.Req).MustStrings(key, defaults...)
}
// QueryInt returns request form as int with default
func (ctx *Context) QueryInt(key string, defaults ...int) int {
return (*Forms)(ctx.Req).MustInt(key, defaults...)
}
// QueryInt64 returns request form as int64 with default
func (ctx *Context) QueryInt64(key string, defaults ...int64) int64 {
return (*Forms)(ctx.Req).MustInt64(key, defaults...)
}
// QueryBool returns request form as bool with default
func (ctx *Context) QueryBool(key string, defaults ...bool) bool {
return (*Forms)(ctx.Req).MustBool(key, defaults...)
}
// HandleText handles HTTP status code // HandleText handles HTTP status code
func (ctx *Context) HandleText(status int, title string) { func (ctx *Context) HandleText(status int, title string) {
if (status/100 == 4) || (status/100 == 5) { if (status/100 == 4) || (status/100 == 5) {
@ -249,66 +314,324 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
ctx.Resp.Header().Set("Cache-Control", "must-revalidate") ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public") ctx.Resp.Header().Set("Pragma", "public")
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition") ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r) http.ServeContent(ctx.Resp, ctx.Req, name, modtime, r)
}
// PlainText render content as plain text
func (ctx *Context) PlainText(status int, bs []byte) {
ctx.Resp.WriteHeader(status)
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf8")
if _, err := ctx.Resp.Write(bs); err != nil {
ctx.ServerError("Render JSON failed", err)
}
}
// ServeFile serves given file to response.
func (ctx *Context) ServeFile(file string, names ...string) {
var name string
if len(names) > 0 {
name = names[0]
} else {
name = path.Base(file)
}
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
http.ServeFile(ctx.Resp, ctx.Req, file)
}
// Error returned an error to web browser
func (ctx *Context) Error(status int, contents ...string) {
var v = http.StatusText(status)
if len(contents) > 0 {
v = contents[0]
}
http.Error(ctx.Resp, v, status)
}
// JSON render content as JSON
func (ctx *Context) JSON(status int, content interface{}) {
ctx.Resp.WriteHeader(status)
ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf8")
if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil {
ctx.ServerError("Render JSON failed", err)
}
}
// Redirect redirect the request
func (ctx *Context) Redirect(location string, status ...int) {
code := http.StatusFound
if len(status) == 1 {
code = status[0]
}
http.Redirect(ctx.Resp, ctx.Req, location, code)
}
// SetCookie set cookies to web browser
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
middlewares.SetCookie(ctx.Resp, name, value, others...)
}
// GetCookie returns given cookie value from request header.
func (ctx *Context) GetCookie(name string) string {
return middlewares.GetCookie(ctx.Req, name)
}
// GetSuperSecureCookie returns given cookie value from request header with secret string.
func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
val := ctx.GetCookie(name)
if val == "" {
return "", false
}
text, err := hex.DecodeString(val)
if err != nil {
return "", false
}
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
text, err = com.AESGCMDecrypt(key, text)
return string(text), err == nil
}
// SetSuperSecureCookie sets given cookie value to response header with secret string.
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
text, err := com.AESGCMEncrypt(key, []byte(value))
if err != nil {
panic("error encrypting cookie: " + err.Error())
}
ctx.SetCookie(name, hex.EncodeToString(text), others...)
}
// GetCookieInt returns cookie result in int type.
func (ctx *Context) GetCookieInt(name string) int {
r, _ := strconv.Atoi(ctx.GetCookie(name))
return r
}
// GetCookieInt64 returns cookie result in int64 type.
func (ctx *Context) GetCookieInt64(name string) int64 {
r, _ := strconv.ParseInt(ctx.GetCookie(name), 10, 64)
return r
}
// GetCookieFloat64 returns cookie result in float64 type.
func (ctx *Context) GetCookieFloat64(name string) float64 {
v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
return v
}
// RemoteAddr returns the client machie ip address
func (ctx *Context) RemoteAddr() string {
return ctx.Req.RemoteAddr
}
// Params returns the param on route
func (ctx *Context) Params(p string) string {
s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":")))
return s
}
// ParamsInt64 returns the param on route as int64
func (ctx *Context) ParamsInt64(p string) int64 {
v, _ := strconv.ParseInt(ctx.Params(p), 10, 64)
return v
}
// SetParams set params into routes
func (ctx *Context) SetParams(k, v string) {
chiCtx := chi.RouteContext(ctx.Req.Context())
chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
}
// Write writes data to webbrowser
func (ctx *Context) Write(bs []byte) (int, error) {
return ctx.Resp.Write(bs)
}
// Written returns true if there are something sent to web browser
func (ctx *Context) Written() bool {
return ctx.Resp.Status() > 0
}
// Status writes status code
func (ctx *Context) Status(status int) {
ctx.Resp.WriteHeader(status)
}
// Handler represents a custom handler
type Handler func(*Context)
// enumerate all content
var (
contextKey interface{} = "default_context"
)
// WithContext set up install context in request
func WithContext(req *http.Request, ctx *Context) *http.Request {
return req.WithContext(context.WithValue(req.Context(), contextKey, ctx))
}
// GetContext retrieves install context from request
func GetContext(req *http.Request) *Context {
return req.Context().Value(contextKey).(*Context)
}
func getCsrfOpts() CsrfOptions {
return CsrfOptions{
Secret: setting.SecretKey,
Cookie: setting.CSRFCookieName,
SetCookie: true,
Secure: setting.SessionConfig.Secure,
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
Header: "X-Csrf-Token",
CookieDomain: setting.SessionConfig.Domain,
CookiePath: setting.SessionConfig.CookiePath,
}
} }
// Contexter initializes a classic context for a request. // Contexter initializes a classic context for a request.
func Contexter() macaron.Handler { func Contexter() func(next http.Handler) http.Handler {
return func(c *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) { rnd := templates.HTMLRenderer()
ctx := &Context{
Context: c,
Cache: cache,
csrf: x,
Flash: f,
Session: sess,
Link: setting.AppSubURL + strings.TrimSuffix(c.Req.URL.EscapedPath(), "/"),
Repo: &Repository{
PullRequest: &PullRequest{},
},
Org: &Organization{},
}
ctx.Data["Language"] = ctx.Locale.Language()
c.Data["Link"] = ctx.Link
ctx.Data["CurrentURL"] = setting.AppSubURL + c.Req.URL.RequestURI()
ctx.Data["PageStartTime"] = time.Now()
// Quick responses appropriate go-get meta with status 200
// regardless of if user have access to the repository,
// or the repository does not exist at all.
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
if ctx.Query("go-get") == "1" {
ownerName := c.Params(":username")
repoName := c.Params(":reponame")
trimmedRepoName := strings.TrimSuffix(repoName, ".git")
if ownerName == "" || trimmedRepoName == "" { var c cache.Cache
_, _ = c.Write([]byte(`<!doctype html> var err error
if setting.CacheService.Enabled {
c, err = cache.NewCacher(cache.Options{
Adapter: setting.CacheService.Adapter,
AdapterConfig: setting.CacheService.Conn,
Interval: setting.CacheService.Interval,
})
if err != nil {
panic(err)
}
}
var csrfOpts = getCsrfOpts()
//var flashEncryptionKey, _ = NewSecret()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
var locale = middlewares.Locale(resp, req)
var startTime = time.Now()
var link = setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
var ctx = Context{
Resp: NewResponse(resp),
Cache: c,
Locale: locale,
Link: link,
Render: rnd,
Session: session.GetSession(req),
Repo: &Repository{
PullRequest: &PullRequest{},
},
Org: &Organization{},
Data: map[string]interface{}{
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
"PageStartTime": startTime,
"TmplLoadTimes": func() string {
return time.Since(startTime).String()
},
"Link": link,
},
}
ctx.Req = WithContext(req, &ctx)
ctx.csrf = Csrfer(csrfOpts, &ctx)
// Get flash.
flashCookie := ctx.GetCookie("macaron_flash")
vals, _ := url.ParseQuery(flashCookie)
if len(vals) > 0 {
f := &middlewares.Flash{
DataStore: &ctx,
Values: vals,
ErrorMsg: vals.Get("error"),
SuccessMsg: vals.Get("success"),
InfoMsg: vals.Get("info"),
WarningMsg: vals.Get("warning"),
}
ctx.Data["Flash"] = f
}
f := &middlewares.Flash{
DataStore: &ctx,
Values: url.Values{},
ErrorMsg: "",
WarningMsg: "",
InfoMsg: "",
SuccessMsg: "",
}
ctx.Resp.Before(func(resp ResponseWriter) {
if flash := f.Encode(); len(flash) > 0 {
if err == nil {
middlewares.SetCookie(resp, "macaron_flash", flash, 0,
setting.SessionConfig.CookiePath,
middlewares.Domain(setting.SessionConfig.Domain),
middlewares.HTTPOnly(true),
middlewares.Secure(setting.SessionConfig.Secure),
//middlewares.SameSite(opt.SameSite), FIXME: we need a samesite config
)
return
}
}
ctx.SetCookie("macaron_flash", "", -1,
setting.SessionConfig.CookiePath,
middlewares.Domain(setting.SessionConfig.Domain),
middlewares.HTTPOnly(true),
middlewares.Secure(setting.SessionConfig.Secure),
//middlewares.SameSite(), FIXME: we need a samesite config
)
})
ctx.Flash = f
// Quick responses appropriate go-get meta with status 200
// regardless of if user have access to the repository,
// or the repository does not exist at all.
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
if ctx.Query("go-get") == "1" {
ownerName := ctx.Params(":username")
repoName := ctx.Params(":reponame")
trimmedRepoName := strings.TrimSuffix(repoName, ".git")
if ownerName == "" || trimmedRepoName == "" {
_, _ = ctx.Write([]byte(`<!doctype html>
<html> <html>
<body> <body>
invalid import path invalid import path
</body> </body>
</html> </html>
`)) `))
c.WriteHeader(400) ctx.Status(400)
return return
} }
branchName := "master" branchName := "master"
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err == nil && len(repo.DefaultBranch) > 0 { if err == nil && len(repo.DefaultBranch) > 0 {
branchName = repo.DefaultBranch branchName = repo.DefaultBranch
} }
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName)) prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
appURL, _ := url.Parse(setting.AppURL) appURL, _ := url.Parse(setting.AppURL)
insecure := "" insecure := ""
if appURL.Scheme == string(setting.HTTP) { if appURL.Scheme == string(setting.HTTP) {
insecure = "--insecure " insecure = "--insecure "
} }
c.Header().Set("Content-Type", "text/html") ctx.Header().Set("Content-Type", "text/html")
c.WriteHeader(http.StatusOK) ctx.Status(http.StatusOK)
_, _ = c.Write([]byte(com.Expand(`<!doctype html> _, _ = ctx.Write([]byte(com.Expand(`<!doctype html>
<html> <html>
<head> <head>
<meta name="go-import" content="{GoGetImport} git {CloneLink}"> <meta name="go-import" content="{GoGetImport} git {CloneLink}">
@ -319,60 +642,72 @@ func Contexter() macaron.Handler {
</body> </body>
</html> </html>
`, map[string]string{ `, map[string]string{
"GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName), "GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
"CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName), "CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
"GoDocDirectory": prefix + "{/dir}", "GoDocDirectory": prefix + "{/dir}",
"GoDocFile": prefix + "{/dir}/{file}#L{line}", "GoDocFile": prefix + "{/dir}/{file}#L{line}",
"Insecure": insecure, "Insecure": insecure,
}))) })))
return
}
// Get user from session if logged in.
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req.Request, c.Resp, ctx, ctx.Session)
if ctx.User != nil {
ctx.IsSigned = true
ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
} else {
ctx.Data["SignedUserID"] = int64(0)
ctx.Data["SignedUserName"] = ""
}
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.ServerError("ParseMultipartForm", err)
return return
} }
}
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
ctx.ServerError("ParseMultipartForm", err)
return
}
}
ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken()) // Get user from session if logged in.
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`) ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
log.Debug("Session ID: %s", sess.ID())
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome if ctx.User != nil {
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore ctx.IsSigned = true
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations ctx.Data["IsSigned"] = ctx.IsSigned
ctx.Data["SignedUser"] = ctx.User
ctx.Data["SignedUserID"] = ctx.User.ID
ctx.Data["SignedUserName"] = ctx.User.Name
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
} else {
ctx.Data["SignedUserID"] = int64(0)
ctx.Data["SignedUserName"] = ""
}
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations log.Debug("Session ID: %s", ctx.Session.ID())
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
ctx.Data["ManifestData"] = setting.ManifestData ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
c.Map(ctx) ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["ManifestData"] = setting.ManifestData
ctx.Data["i18n"] = locale
ctx.Data["Tr"] = i18n.Tr
ctx.Data["Lang"] = locale.Language()
ctx.Data["AllLangs"] = translation.AllLangs()
for _, lang := range translation.AllLangs() {
if lang.Lang == locale.Language() {
ctx.Data["LangName"] = lang.Name
break
}
}
next.ServeHTTP(ctx.Resp, ctx.Req)
})
} }
} }

View file

@ -1,5 +1,6 @@
// Copyright 2013 Martini Authors // Copyright 2013 Martini Authors
// Copyright 2014 The Macaron Authors // Copyright 2014 The Macaron Authors
// Copyright 2021 The Gitea Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"): you may // Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain // not use this file except in compliance with the License. You may obtain
@ -13,24 +14,17 @@
// License for the specific language governing permissions and limitations // License for the specific language governing permissions and limitations
// under the License. // under the License.
// Package csrf is a middleware that generates and validates CSRF tokens for Macaron. // a middleware that generates and validates CSRF tokens.
package csrf
package context
import ( import (
"net/http" "net/http"
"time" "time"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
"github.com/unknwon/com" "github.com/unknwon/com"
) )
const _VERSION = "0.1.1"
func Version() string {
return _VERSION
}
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token. // CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
type CSRF interface { type CSRF interface {
// Return HTTP header to search for token. // Return HTTP header to search for token.
@ -42,7 +36,7 @@ type CSRF interface {
// Return cookie path // Return cookie path
GetCookiePath() string GetCookiePath() string
// Return the flag value used for the csrf token. // Return the flag value used for the csrf token.
GetCookieHttpOnly() bool GetCookieHTTPOnly() bool
// Return the token. // Return the token.
GetToken() string GetToken() string
// Validate by token. // Validate by token.
@ -63,7 +57,7 @@ type csrf struct {
//Cookie path //Cookie path
CookiePath string CookiePath string
// Cookie HttpOnly flag value used for the csrf token. // Cookie HttpOnly flag value used for the csrf token.
CookieHttpOnly bool CookieHTTPOnly bool
// Token generated to pass via header, cookie, or hidden form value. // Token generated to pass via header, cookie, or hidden form value.
Token string Token string
// This value must be unique per user. // This value must be unique per user.
@ -94,9 +88,9 @@ func (c *csrf) GetCookiePath() string {
return c.CookiePath return c.CookiePath
} }
// GetCookieHttpOnly returns the flag value used for the csrf token. // GetCookieHTTPOnly returns the flag value used for the csrf token.
func (c *csrf) GetCookieHttpOnly() bool { func (c *csrf) GetCookieHTTPOnly() bool {
return c.CookieHttpOnly return c.CookieHTTPOnly
} }
// GetToken returns the current token. This is typically used // GetToken returns the current token. This is typically used
@ -115,8 +109,8 @@ func (c *csrf) Error(w http.ResponseWriter) {
c.ErrorFunc(w) c.ErrorFunc(w)
} }
// Options maintains options to manage behavior of Generate. // CsrfOptions maintains options to manage behavior of Generate.
type Options struct { type CsrfOptions struct {
// The global secret value used to generate Tokens. // The global secret value used to generate Tokens.
Secret string Secret string
// HTTP header used to set and get token. // HTTP header used to set and get token.
@ -129,11 +123,13 @@ type Options struct {
CookieDomain string CookieDomain string
// Cookie path. // Cookie path.
CookiePath string CookiePath string
CookieHttpOnly bool CookieHTTPOnly bool
// SameSite set the cookie SameSite type
SameSite http.SameSite
// Key used for getting the unique ID per user. // Key used for getting the unique ID per user.
SessionKey string SessionKey string
// oldSeesionKey saves old value corresponding to SessionKey. // oldSessionKey saves old value corresponding to SessionKey.
oldSeesionKey string oldSessionKey string
// If true, send token via X-CSRFToken header. // If true, send token via X-CSRFToken header.
SetHeader bool SetHeader bool
// If true, send token via _csrf cookie. // If true, send token via _csrf cookie.
@ -144,10 +140,12 @@ type Options struct {
Origin bool Origin bool
// The function called when Validate fails. // The function called when Validate fails.
ErrorFunc func(w http.ResponseWriter) ErrorFunc func(w http.ResponseWriter)
// Cookie life time. Default is 0
CookieLifeTime int
} }
func prepareOptions(options []Options) Options { func prepareOptions(options []CsrfOptions) CsrfOptions {
var opt Options var opt CsrfOptions
if len(options) > 0 { if len(options) > 0 {
opt = options[0] opt = options[0]
} }
@ -171,7 +169,7 @@ func prepareOptions(options []Options) Options {
if len(opt.SessionKey) == 0 { if len(opt.SessionKey) == 0 {
opt.SessionKey = "uid" opt.SessionKey = "uid"
} }
opt.oldSeesionKey = "_old_" + opt.SessionKey opt.oldSessionKey = "_old_" + opt.SessionKey
if opt.ErrorFunc == nil { if opt.ErrorFunc == nil {
opt.ErrorFunc = func(w http.ResponseWriter) { opt.ErrorFunc = func(w http.ResponseWriter) {
http.Error(w, "Invalid csrf token.", http.StatusBadRequest) http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
@ -181,73 +179,73 @@ func prepareOptions(options []Options) Options {
return opt return opt
} }
// Generate maps CSRF to each request. If this request is a Get request, it will generate a new token.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func Generate(options ...Options) macaron.Handler {
opt := prepareOptions(options)
return func(ctx *macaron.Context, sess session.Store) {
x := &csrf{
Secret: opt.Secret,
Header: opt.Header,
Form: opt.Form,
Cookie: opt.Cookie,
CookieDomain: opt.CookieDomain,
CookiePath: opt.CookiePath,
CookieHttpOnly: opt.CookieHttpOnly,
ErrorFunc: opt.ErrorFunc,
}
ctx.MapTo(x, (*CSRF)(nil))
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
return
}
x.ID = "0"
uid := sess.Get(opt.SessionKey)
if uid != nil {
x.ID = com.ToStr(uid)
}
needsNew := false
oldUid := sess.Get(opt.oldSeesionKey)
if oldUid == nil || oldUid.(string) != x.ID {
needsNew = true
sess.Set(opt.oldSeesionKey, x.ID)
} else {
// If cookie present, map existing token, else generate a new one.
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
// FIXME: test coverage.
x.Token = val
} else {
needsNew = true
}
}
if needsNew {
// FIXME: actionId.
x.Token = GenerateToken(x.Secret, x.ID, "POST")
if opt.SetCookie {
ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHttpOnly, time.Now().AddDate(0, 0, 1))
}
}
if opt.SetHeader {
ctx.Resp.Header().Add(opt.Header, x.Token)
}
}
}
// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token. // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func Csrfer(options ...Options) macaron.Handler { func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
return Generate(options...) opt = prepareOptions([]CsrfOptions{opt})
x := &csrf{
Secret: opt.Secret,
Header: opt.Header,
Form: opt.Form,
Cookie: opt.Cookie,
CookieDomain: opt.CookieDomain,
CookiePath: opt.CookiePath,
CookieHTTPOnly: opt.CookieHTTPOnly,
ErrorFunc: opt.ErrorFunc,
}
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
return x
}
x.ID = "0"
uid := ctx.Session.Get(opt.SessionKey)
if uid != nil {
x.ID = com.ToStr(uid)
}
needsNew := false
oldUID := ctx.Session.Get(opt.oldSessionKey)
if oldUID == nil || oldUID.(string) != x.ID {
needsNew = true
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
} else {
// If cookie present, map existing token, else generate a new one.
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
// FIXME: test coverage.
x.Token = val
} else {
needsNew = true
}
}
if needsNew {
// FIXME: actionId.
x.Token = GenerateToken(x.Secret, x.ID, "POST")
if opt.SetCookie {
var expires interface{}
if opt.CookieLifeTime == 0 {
expires = time.Now().AddDate(0, 0, 1)
}
ctx.SetCookie(opt.Cookie, x.Token, opt.CookieLifeTime, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHTTPOnly, expires,
func(c *http.Cookie) {
c.SameSite = opt.SameSite
},
)
}
}
if opt.SetHeader {
ctx.Resp.Header().Add(opt.Header, x.Token)
}
return x
} }
// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken" // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
// using ValidToken. If this validation fails, custom Error is sent in the reply. // using ValidToken. If this validation fails, custom Error is sent in the reply.
// If neither a header or form value is found, http.StatusBadRequest is sent. // If neither a header or form value is found, http.StatusBadRequest is sent.
func Validate(ctx *macaron.Context, x CSRF) { func Validate(ctx *Context, x CSRF) {
if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 { if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
if !x.ValidToken(token) { if !x.ValidToken(token) {
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath()) ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())

227
modules/context/form.go Normal file
View file

@ -0,0 +1,227 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"errors"
"net/http"
"net/url"
"strconv"
"strings"
"text/template"
"code.gitea.io/gitea/modules/log"
)
// Forms a new enhancement of http.Request
type Forms http.Request
// Values returns http.Request values
func (f *Forms) Values() url.Values {
return (*http.Request)(f).Form
}
// String returns request form as string
func (f *Forms) String(key string) (string, error) {
return (*http.Request)(f).FormValue(key), nil
}
// Trimmed returns request form as string with trimed spaces left and right
func (f *Forms) Trimmed(key string) (string, error) {
return strings.TrimSpace((*http.Request)(f).FormValue(key)), nil
}
// Strings returns request form as strings
func (f *Forms) Strings(key string) ([]string, error) {
if (*http.Request)(f).Form == nil {
if err := (*http.Request)(f).ParseMultipartForm(32 << 20); err != nil {
return nil, err
}
}
if v, ok := (*http.Request)(f).Form[key]; ok {
return v, nil
}
return nil, errors.New("not exist")
}
// Escape returns request form as escaped string
func (f *Forms) Escape(key string) (string, error) {
return template.HTMLEscapeString((*http.Request)(f).FormValue(key)), nil
}
// Int returns request form as int
func (f *Forms) Int(key string) (int, error) {
return strconv.Atoi((*http.Request)(f).FormValue(key))
}
// Int32 returns request form as int32
func (f *Forms) Int32(key string) (int32, error) {
v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
return int32(v), err
}
// Int64 returns request form as int64
func (f *Forms) Int64(key string) (int64, error) {
return strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
}
// Uint returns request form as uint
func (f *Forms) Uint(key string) (uint, error) {
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
return uint(v), err
}
// Uint32 returns request form as uint32
func (f *Forms) Uint32(key string) (uint32, error) {
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
return uint32(v), err
}
// Uint64 returns request form as uint64
func (f *Forms) Uint64(key string) (uint64, error) {
return strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
}
// Bool returns request form as bool
func (f *Forms) Bool(key string) (bool, error) {
return strconv.ParseBool((*http.Request)(f).FormValue(key))
}
// Float32 returns request form as float32
func (f *Forms) Float32(key string) (float32, error) {
v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
return float32(v), err
}
// Float64 returns request form as float64
func (f *Forms) Float64(key string) (float64, error) {
return strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
}
// MustString returns request form as string with default
func (f *Forms) MustString(key string, defaults ...string) string {
if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
return v
}
if len(defaults) > 0 {
return defaults[0]
}
return ""
}
// MustTrimmed returns request form as string with default
func (f *Forms) MustTrimmed(key string, defaults ...string) string {
return strings.TrimSpace(f.MustString(key, defaults...))
}
// MustStrings returns request form as strings with default
func (f *Forms) MustStrings(key string, defaults ...[]string) []string {
if (*http.Request)(f).Form == nil {
if err := (*http.Request)(f).ParseMultipartForm(32 << 20); err != nil {
log.Error("ParseMultipartForm: %v", err)
return []string{}
}
}
if v, ok := (*http.Request)(f).Form[key]; ok {
return v
}
if len(defaults) > 0 {
return defaults[0]
}
return []string{}
}
// MustEscape returns request form as escaped string with default
func (f *Forms) MustEscape(key string, defaults ...string) string {
if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
return template.HTMLEscapeString(v)
}
if len(defaults) > 0 {
return defaults[0]
}
return ""
}
// MustInt returns request form as int with default
func (f *Forms) MustInt(key string, defaults ...int) int {
v, err := strconv.Atoi((*http.Request)(f).FormValue(key))
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return v
}
// MustInt32 returns request form as int32 with default
func (f *Forms) MustInt32(key string, defaults ...int32) int32 {
v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return int32(v)
}
// MustInt64 returns request form as int64 with default
func (f *Forms) MustInt64(key string, defaults ...int64) int64 {
v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return v
}
// MustUint returns request form as uint with default
func (f *Forms) MustUint(key string, defaults ...uint) uint {
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return uint(v)
}
// MustUint32 returns request form as uint32 with default
func (f *Forms) MustUint32(key string, defaults ...uint32) uint32 {
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return uint32(v)
}
// MustUint64 returns request form as uint64 with default
func (f *Forms) MustUint64(key string, defaults ...uint64) uint64 {
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return v
}
// MustFloat32 returns request form as float32 with default
func (f *Forms) MustFloat32(key string, defaults ...float32) float32 {
v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 32)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return float32(v)
}
// MustFloat64 returns request form as float64 with default
func (f *Forms) MustFloat64(key string, defaults ...float64) float64 {
v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return v
}
// MustBool returns request form as bool with default
func (f *Forms) MustBool(key string, defaults ...bool) bool {
v, err := strconv.ParseBool((*http.Request)(f).FormValue(key))
if len(defaults) > 0 && err != nil {
return defaults[0]
}
return v
}

View file

@ -10,8 +10,6 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/macaron"
) )
// Organization contains organization context // Organization contains organization context
@ -173,7 +171,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
} }
// OrgAssignment returns a macaron middleware to handle organization assignment // OrgAssignment returns a macaron middleware to handle organization assignment
func OrgAssignment(args ...bool) macaron.Handler { func OrgAssignment(args ...bool) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
HandleOrgAssignment(ctx, args...) HandleOrgAssignment(ctx, args...)
} }

View file

@ -7,12 +7,10 @@ package context
import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"gitea.com/macaron/macaron"
) )
// RequireRepoAdmin returns a macaron middleware for requiring repository admin permission // RequireRepoAdmin returns a macaron middleware for requiring repository admin permission
func RequireRepoAdmin() macaron.Handler { func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() { if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
@ -22,7 +20,7 @@ func RequireRepoAdmin() macaron.Handler {
} }
// RequireRepoWriter returns a macaron middleware for requiring repository write to the specify unitType // RequireRepoWriter returns a macaron middleware for requiring repository write to the specify unitType
func RequireRepoWriter(unitType models.UnitType) macaron.Handler { func RequireRepoWriter(unitType models.UnitType) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.Repo.CanWrite(unitType) { if !ctx.Repo.CanWrite(unitType) {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
@ -32,7 +30,7 @@ func RequireRepoWriter(unitType models.UnitType) macaron.Handler {
} }
// RequireRepoWriterOr returns a macaron middleware for requiring repository write to one of the unit permission // RequireRepoWriterOr returns a macaron middleware for requiring repository write to one of the unit permission
func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler { func RequireRepoWriterOr(unitTypes ...models.UnitType) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
for _, unitType := range unitTypes { for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) { if ctx.Repo.CanWrite(unitType) {
@ -44,7 +42,7 @@ func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler {
} }
// RequireRepoReader returns a macaron middleware for requiring repository read to the specify unitType // RequireRepoReader returns a macaron middleware for requiring repository read to the specify unitType
func RequireRepoReader(unitType models.UnitType) macaron.Handler { func RequireRepoReader(unitType models.UnitType) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.Repo.CanRead(unitType) { if !ctx.Repo.CanRead(unitType) {
if log.IsTrace() { if log.IsTrace() {
@ -70,7 +68,7 @@ func RequireRepoReader(unitType models.UnitType) macaron.Handler {
} }
// RequireRepoReaderOr returns a macaron middleware for requiring repository write to one of the unit permission // RequireRepoReaderOr returns a macaron middleware for requiring repository write to one of the unit permission
func RequireRepoReaderOr(unitTypes ...models.UnitType) macaron.Handler { func RequireRepoReaderOr(unitTypes ...models.UnitType) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
for _, unitType := range unitTypes { for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) { if ctx.Repo.CanRead(unitType) {

View file

@ -0,0 +1,45 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"context"
"net/http"
)
// PrivateContext represents a context for private routes
type PrivateContext struct {
*Context
}
var (
privateContextKey interface{} = "default_private_context"
)
// WithPrivateContext set up private context in request
func WithPrivateContext(req *http.Request, ctx *PrivateContext) *http.Request {
return req.WithContext(context.WithValue(req.Context(), privateContextKey, ctx))
}
// GetPrivateContext returns a context for Private routes
func GetPrivateContext(req *http.Request) *PrivateContext {
return req.Context().Value(privateContextKey).(*PrivateContext)
}
// PrivateContexter returns apicontext as macaron middleware
func PrivateContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := &PrivateContext{
Context: &Context{
Resp: NewResponse(w),
Data: map[string]interface{}{},
},
}
ctx.Req = WithPrivateContext(req, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}

View file

@ -8,6 +8,7 @@ package context
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"net/url" "net/url"
"path" "path"
"strings" "strings"
@ -21,7 +22,6 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
"github.com/editorconfig/editorconfig-core-go/v2" "github.com/editorconfig/editorconfig-core-go/v2"
"github.com/unknwon/com" "github.com/unknwon/com"
) )
@ -81,7 +81,7 @@ func (r *Repository) CanCreateBranch() bool {
} }
// RepoMustNotBeArchived checks if a repo is archived // RepoMustNotBeArchived checks if a repo is archived
func RepoMustNotBeArchived() macaron.Handler { func RepoMustNotBeArchived() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if ctx.Repo.Repository.IsArchived { if ctx.Repo.Repository.IsArchived {
ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title"))) ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
@ -374,7 +374,7 @@ func repoAssignment(ctx *Context, repo *models.Repository) {
} }
// RepoIDAssignment returns a macaron handler which assigns the repo to the context. // RepoIDAssignment returns a macaron handler which assigns the repo to the context.
func RepoIDAssignment() macaron.Handler { func RepoIDAssignment() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
repoID := ctx.ParamsInt64(":repoid") repoID := ctx.ParamsInt64(":repoid")
@ -394,223 +394,220 @@ func RepoIDAssignment() macaron.Handler {
} }
// RepoAssignment returns a macaron to handle repository assignment // RepoAssignment returns a macaron to handle repository assignment
func RepoAssignment() macaron.Handler { func RepoAssignment() func(http.Handler) http.Handler {
return func(ctx *Context) { return func(next http.Handler) http.Handler {
var ( return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
owner *models.User var (
err error owner *models.User
) err error
ctx = GetContext(req)
)
userName := ctx.Params(":username") userName := ctx.Params(":username")
repoName := ctx.Params(":reponame") repoName := ctx.Params(":reponame")
repoName = strings.TrimSuffix(repoName, ".git")
// Check if the user is the same as the repository owner // Check if the user is the same as the repository owner
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) { if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
owner = ctx.User owner = ctx.User
} else { } else {
owner, err = models.GetUserByName(userName) owner, err = models.GetUserByName(userName)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
redirectUserID, err := models.LookupUserRedirect(userName)
if err == nil {
RedirectToUser(ctx, userName, redirectUserID)
} else if models.IsErrUserRedirectNotExist(err) {
if ctx.Query("go-get") == "1" { if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx) EarlyResponseForGoGetMeta(ctx)
return return
} }
ctx.NotFound("GetUserByName", nil) ctx.NotFound("GetUserByName", nil)
} else { } else {
ctx.ServerError("LookupUserRedirect", err) ctx.ServerError("GetUserByName", err)
}
return
}
}
ctx.Repo.Owner = owner
ctx.Data["Username"] = ctx.Repo.Owner.Name
// Get repository.
repo, err := models.GetRepositoryByName(owner.ID, repoName)
if err != nil {
if models.IsErrRepoNotExist(err) {
redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
if err == nil {
RedirectToRepo(ctx, redirectRepoID)
} else if models.IsErrRepoRedirectNotExist(err) {
if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
}
ctx.NotFound("GetRepositoryByName", nil)
} else {
ctx.ServerError("LookupRepoRedirect", err)
} }
} else { } else {
ctx.ServerError("GetUserByName", err) ctx.ServerError("GetRepositoryByName", err)
} }
return return
} }
} repo.Owner = owner
ctx.Repo.Owner = owner
ctx.Data["Username"] = ctx.Repo.Owner.Name
// Get repository. repoAssignment(ctx, repo)
repo, err := models.GetRepositoryByName(owner.ID, repoName) if ctx.Written() {
if err != nil { return
if models.IsErrRepoNotExist(err) {
redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
if err == nil {
RedirectToRepo(ctx, redirectRepoID)
} else if models.IsErrRepoRedirectNotExist(err) {
if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
}
ctx.NotFound("GetRepositoryByName", nil)
} else {
ctx.ServerError("LookupRepoRedirect", err)
}
} else {
ctx.ServerError("GetRepositoryByName", err)
} }
return
}
repo.Owner = owner
repoAssignment(ctx, repo) ctx.Repo.RepoLink = repo.Link()
if ctx.Written() { ctx.Data["RepoLink"] = ctx.Repo.RepoLink
return ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
}
ctx.Repo.RepoLink = repo.Link() unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
ctx.Data["RepoLink"] = ctx.Repo.RepoLink if err == nil {
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
}
unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker) ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
if err == nil { IncludeTags: true,
ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL })
} if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
return
}
ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
return
}
ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ ctx.Data["Title"] = owner.Name + "/" + repo.Name
IncludeTags: true, ctx.Data["Repository"] = repo
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
ctx.ServerError("CanUserFork", err)
return
}
ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["CloneLink"] = repo.CloneLink()
ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
if ctx.IsSigned {
ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
}
if repo.IsFork {
RetrieveBaseRepo(ctx, repo)
if ctx.Written() {
return
}
}
if repo.IsGenerated() {
RetrieveTemplateRepo(ctx, repo)
if ctx.Written() {
return
}
}
// Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
return
}
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
if err != nil {
ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
return
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
defer func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
next.ServeHTTP(w, req)
return
}
tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil {
ctx.ServerError("GetTags", err)
return
}
ctx.Data["Tags"] = tags
brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return
}
ctx.Data["Branches"] = brs
ctx.Data["BranchesCount"] = len(brs)
ctx.Data["TagName"] = ctx.Repo.TagName
// If not branch selected, try default one.
// If default branch doesn't exists, fall back to some other branch.
if len(ctx.Repo.BranchName) == 0 {
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
} else if len(brs) > 0 {
ctx.Repo.BranchName = brs[0]
}
}
ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["CommitID"] = ctx.Repo.CommitID
// People who have push access or have forked repository can propose a new pull request.
canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
canCompare := false
// Pull request is allowed if this is a fork repository
// and base repository accepts pull requests.
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
canCompare = true
ctx.Data["BaseRepo"] = repo.BaseRepo
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
} else if repo.AllowsPulls() {
// Or, this is repository accepts pull requests between branches.
canCompare = true
ctx.Data["BaseRepo"] = repo
ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.SameRepo = true
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
}
ctx.Data["CanCompareOrPull"] = canCompare
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
if ctx.Query("go-get") == "1" {
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
}
next.ServeHTTP(w, req)
}) })
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
return
}
ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
return
}
ctx.Data["Title"] = owner.Name + "/" + repo.Name
ctx.Data["Repository"] = repo
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
ctx.ServerError("CanUserFork", err)
return
}
ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["CloneLink"] = repo.CloneLink()
ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
if ctx.IsSigned {
ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
}
if repo.IsFork {
RetrieveBaseRepo(ctx, repo)
if ctx.Written() {
return
}
}
if repo.IsGenerated() {
RetrieveTemplateRepo(ctx, repo)
if ctx.Written() {
return
}
}
// Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
return
}
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
if err != nil {
ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
return
}
ctx.Repo.GitRepo = gitRepo
// We opened it, we should close it
defer func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
ctx.Next()
return
}
tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil {
ctx.ServerError("GetTags", err)
return
}
ctx.Data["Tags"] = tags
brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return
}
ctx.Data["Branches"] = brs
ctx.Data["BranchesCount"] = len(brs)
ctx.Data["TagName"] = ctx.Repo.TagName
// If not branch selected, try default one.
// If default branch doesn't exists, fall back to some other branch.
if len(ctx.Repo.BranchName) == 0 {
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
} else if len(brs) > 0 {
ctx.Repo.BranchName = brs[0]
}
}
ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["CommitID"] = ctx.Repo.CommitID
// People who have push access or have forked repository can propose a new pull request.
canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
canCompare := false
// Pull request is allowed if this is a fork repository
// and base repository accepts pull requests.
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
canCompare = true
ctx.Data["BaseRepo"] = repo.BaseRepo
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
} else if repo.AllowsPulls() {
// Or, this is repository accepts pull requests between branches.
canCompare = true
ctx.Data["BaseRepo"] = repo
ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.SameRepo = true
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
}
ctx.Data["CanCompareOrPull"] = canCompare
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
if ctx.Query("go-get") == "1" {
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
}
ctx.Next()
} }
} }
@ -636,7 +633,7 @@ const (
// RepoRef handles repository reference names when the ref name is not // RepoRef handles repository reference names when the ref name is not
// explicitly given // explicitly given
func RepoRef() macaron.Handler { func RepoRef() func(http.Handler) http.Handler {
// since no ref name is explicitly specified, ok to just use branch // since no ref name is explicitly specified, ok to just use branch
return RepoRefByType(RepoRefBranch) return RepoRefByType(RepoRefBranch)
} }
@ -715,132 +712,135 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
// RepoRefByType handles repository reference name for a specific type // RepoRefByType handles repository reference name for a specific type
// of repository reference // of repository reference
func RepoRefByType(refType RepoRefType) macaron.Handler { func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler {
return func(ctx *Context) { return func(next http.Handler) http.Handler {
// Empty repository does not have reference information. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if ctx.Repo.Repository.IsEmpty { ctx := GetContext(req)
return // Empty repository does not have reference information.
} if ctx.Repo.Repository.IsEmpty {
var (
refName string
err error
)
if ctx.Repo.GitRepo == nil {
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
if err != nil {
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return return
} }
// We opened it, we should close it
defer func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
}
// Get default branch. var (
if len(ctx.Params("*")) == 0 { refName string
refName = ctx.Repo.Repository.DefaultBranch err error
ctx.Repo.BranchName = refName )
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
brs, err := ctx.Repo.GitRepo.GetBranches() if ctx.Repo.GitRepo == nil {
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
if err != nil { if err != nil {
ctx.ServerError("GetBranches", err) ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return
} else if len(brs) == 0 {
err = fmt.Errorf("No branches in non-empty repository %s",
ctx.Repo.GitRepo.Path)
ctx.ServerError("GetBranches", err)
return return
} }
refName = brs[0] // We opened it, we should close it
defer func() {
// If it's been set to nil then assume someone else has closed it.
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
}
}()
} }
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
ctx.Repo.IsViewBranch = true
} else {
refName = getRefName(ctx, refType)
ctx.Repo.BranchName = refName
if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.IsViewBranch = true
// Get default branch.
if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch
ctx.Repo.BranchName = refName
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil {
ctx.ServerError("GetBranches", err)
return
} else if len(brs) == 0 {
err = fmt.Errorf("No branches in non-empty repository %s",
ctx.Repo.GitRepo.Path)
ctx.ServerError("GetBranches", err)
return
}
refName = brs[0]
}
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil { if err != nil {
ctx.ServerError("GetBranchCommit", err) ctx.ServerError("GetBranchCommit", err)
return return
} }
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
ctx.Repo.IsViewBranch = true
} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
ctx.Repo.IsViewTag = true
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
if err != nil {
ctx.ServerError("GetTagCommit", err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if len(refName) >= 7 && len(refName) <= 40 {
ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil {
ctx.NotFound("GetCommit", err)
return
}
// If short commit ID add canonical link header
if len(refName) < 40 {
ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
}
} else { } else {
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) refName = getRefName(ctx, refType)
return ctx.Repo.BranchName = refName
if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.IsViewBranch = true
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
ctx.Repo.IsViewTag = true
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
if err != nil {
ctx.ServerError("GetTagCommit", err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if len(refName) >= 7 && len(refName) <= 40 {
ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil {
ctx.NotFound("GetCommit", err)
return
}
// If short commit ID add canonical link header
if len(refName) < 40 {
ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
}
} else {
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
return
}
if refType == RepoRefLegacy {
// redirect from old URL scheme to new URL scheme
ctx.Redirect(path.Join(
setting.AppSubURL,
strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
ctx.Repo.BranchNameSubURL(),
ctx.Repo.TreePath))
return
}
} }
if refType == RepoRefLegacy { ctx.Data["BranchName"] = ctx.Repo.BranchName
// redirect from old URL scheme to new URL scheme ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
ctx.Redirect(path.Join( ctx.Data["CommitID"] = ctx.Repo.CommitID
setting.AppSubURL, ctx.Data["TreePath"] = ctx.Repo.TreePath
strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
ctx.Repo.BranchNameSubURL(), ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
ctx.Repo.TreePath)) ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
return return
} }
} ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Data["BranchName"] = ctx.Repo.BranchName next.ServeHTTP(w, req)
ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() })
ctx.Data["CommitID"] = ctx.Repo.CommitID
ctx.Data["TreePath"] = ctx.Repo.TreePath
ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
return
}
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Next()
} }
} }
// GitHookService checks if repository Git hooks service has been enabled. // GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler { func GitHookService() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.User.CanEditGitHook() { if !ctx.User.CanEditGitHook() {
ctx.NotFound("GitHookService", nil) ctx.NotFound("GitHookService", nil)
@ -850,7 +850,7 @@ func GitHookService() macaron.Handler {
} }
// UnitTypes returns a macaron middleware to set unit types to context variables. // UnitTypes returns a macaron middleware to set unit types to context variables.
func UnitTypes() macaron.Handler { func UnitTypes() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
ctx.Data["UnitTypeCode"] = models.UnitTypeCode ctx.Data["UnitTypeCode"] = models.UnitTypeCode
ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues

View file

@ -11,6 +11,7 @@ type ResponseWriter interface {
http.ResponseWriter http.ResponseWriter
Flush() Flush()
Status() int Status() int
Before(func(ResponseWriter))
} }
var ( var (
@ -20,11 +21,19 @@ var (
// Response represents a response // Response represents a response
type Response struct { type Response struct {
http.ResponseWriter http.ResponseWriter
status int status int
befores []func(ResponseWriter)
beforeExecuted bool
} }
// Write writes bytes to HTTP endpoint // Write writes bytes to HTTP endpoint
func (r *Response) Write(bs []byte) (int, error) { func (r *Response) Write(bs []byte) (int, error) {
if !r.beforeExecuted {
for _, before := range r.befores {
before(r)
}
r.beforeExecuted = true
}
size, err := r.ResponseWriter.Write(bs) size, err := r.ResponseWriter.Write(bs)
if err != nil { if err != nil {
return 0, err return 0, err
@ -37,6 +46,12 @@ func (r *Response) Write(bs []byte) (int, error) {
// WriteHeader write status code // WriteHeader write status code
func (r *Response) WriteHeader(statusCode int) { func (r *Response) WriteHeader(statusCode int) {
if !r.beforeExecuted {
for _, before := range r.befores {
before(r)
}
r.beforeExecuted = true
}
r.status = statusCode r.status = statusCode
r.ResponseWriter.WriteHeader(statusCode) r.ResponseWriter.WriteHeader(statusCode)
} }
@ -53,10 +68,20 @@ func (r *Response) Status() int {
return r.status return r.status
} }
// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
func (r *Response) Before(f func(ResponseWriter)) {
r.befores = append(r.befores, f)
}
// NewResponse creates a response // NewResponse creates a response
func NewResponse(resp http.ResponseWriter) *Response { func NewResponse(resp http.ResponseWriter) *Response {
if v, ok := resp.(*Response); ok { if v, ok := resp.(*Response); ok {
return v return v
} }
return &Response{resp, 0} return &Response{
ResponseWriter: resp,
status: 0,
befores: make([]func(ResponseWriter), 0),
}
} }

100
modules/context/secret.go Normal file
View file

@ -0,0 +1,100 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package context
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"io"
)
// NewSecret creates a new secret
func NewSecret() (string, error) {
return NewSecretWithLength(32)
}
// NewSecretWithLength creates a new secret for a given length
func NewSecretWithLength(length int64) (string, error) {
return randomString(length)
}
func randomBytes(len int64) ([]byte, error) {
b := make([]byte, len)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
func randomString(len int64) (string, error) {
b, err := randomBytes(len)
return base64.URLEncoding.EncodeToString(b), err
}
// AesEncrypt encrypts text and given key with AES.
func AesEncrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
}
// AesDecrypt decrypts text and given key with AES.
func AesDecrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(text) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return nil, err
}
return data, nil
}
// EncryptSecret encrypts a string with given key into a hex string
func EncryptSecret(key string, str string) (string, error) {
keyHash := sha256.Sum256([]byte(key))
plaintext := []byte(str)
ciphertext, err := AesEncrypt(keyHash[:], plaintext)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// DecryptSecret decrypts a previously encrypted hex string
func DecryptSecret(key string, cipherhex string) (string, error) {
keyHash := sha256.Sum256([]byte(key))
ciphertext, err := base64.StdEncoding.DecodeString(cipherhex)
if err != nil {
return "", err
}
plaintext, err := AesDecrypt(keyHash[:], ciphertext)
if err != nil {
return "", err
}
return string(plaintext), nil
}

View file

@ -1,5 +1,6 @@
// Copyright 2012 Google Inc. All Rights Reserved. // Copyright 2012 Google Inc. All Rights Reserved.
// Copyright 2014 The Macaron Authors // Copyright 2014 The Macaron Authors
// Copyright 2020 The Gitea Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -13,7 +14,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package csrf package context
import ( import (
"bytes" "bytes"
@ -27,13 +28,13 @@ import (
"time" "time"
) )
// The duration that XSRF tokens are valid. // Timeout represents the duration that XSRF tokens are valid.
// It is exported so clients may set cookie timeouts that match generated tokens. // It is exported so clients may set cookie timeouts that match generated tokens.
const TIMEOUT = 24 * time.Hour const Timeout = 24 * time.Hour
// clean sanitizes a string for inclusion in a token by replacing all ":"s. // clean sanitizes a string for inclusion in a token by replacing all ":"s.
func clean(s string) string { func clean(s string) string {
return strings.Replace(s, ":", "_", -1) return strings.ReplaceAll(s, ":", "_")
} }
// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours. // GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours.
@ -53,7 +54,7 @@ func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
return base64.RawURLEncoding.EncodeToString([]byte(tok)) return base64.RawURLEncoding.EncodeToString([]byte(tok))
} }
// Valid returns true if token is a valid, unexpired token returned by Generate. // ValidToken returns true if token is a valid, unexpired token returned by Generate.
func ValidToken(token, key, userID, actionID string) bool { func ValidToken(token, key, userID, actionID string) bool {
return validTokenAtTime(token, key, userID, actionID, time.Now()) return validTokenAtTime(token, key, userID, actionID, time.Now())
} }
@ -78,7 +79,7 @@ func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
issueTime := time.Unix(0, nanos) issueTime := time.Unix(0, nanos)
// Check that the token is not expired. // Check that the token is not expired.
if now.Sub(issueTime) >= TIMEOUT { if now.Sub(issueTime) >= Timeout {
return false return false
} }

View file

@ -0,0 +1,90 @@
// Copyright 2012 Google Inc. All Rights Reserved.
// Copyright 2014 The Macaron Authors
// Copyright 2020 The Gitea Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package context
import (
"encoding/base64"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const (
key = "quay"
userID = "12345678"
actionID = "POST /form"
)
var (
now = time.Now()
oneMinuteFromNow = now.Add(1 * time.Minute)
)
func Test_ValidToken(t *testing.T) {
t.Run("Validate token", func(t *testing.T) {
tok := generateTokenAtTime(key, userID, actionID, now)
assert.True(t, validTokenAtTime(tok, key, userID, actionID, oneMinuteFromNow))
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(Timeout-1*time.Nanosecond)))
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(-1*time.Minute)))
})
}
// Test_SeparatorReplacement tests that separators are being correctly substituted
func Test_SeparatorReplacement(t *testing.T) {
t.Run("Test two separator replacements", func(t *testing.T) {
assert.NotEqual(t, generateTokenAtTime("foo:bar", "baz", "wah", now),
generateTokenAtTime("foo", "bar:baz", "wah", now))
})
}
func Test_InvalidToken(t *testing.T) {
t.Run("Test invalid tokens", func(t *testing.T) {
invalidTokenTests := []struct {
name, key, userID, actionID string
t time.Time
}{
{"Bad key", "foobar", userID, actionID, oneMinuteFromNow},
{"Bad userID", key, "foobar", actionID, oneMinuteFromNow},
{"Bad actionID", key, userID, "foobar", oneMinuteFromNow},
{"Expired", key, userID, actionID, now.Add(Timeout)},
{"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)},
}
tok := generateTokenAtTime(key, userID, actionID, now)
for _, itt := range invalidTokenTests {
assert.False(t, validTokenAtTime(tok, itt.key, itt.userID, itt.actionID, itt.t))
}
})
}
// Test_ValidateBadData primarily tests that no unexpected panics are triggered during parsing
func Test_ValidateBadData(t *testing.T) {
t.Run("Validate bad data", func(t *testing.T) {
badDataTests := []struct {
name, tok string
}{
{"Invalid Base64", "ASDab24(@)$*=="},
{"No delimiter", base64.URLEncoding.EncodeToString([]byte("foobar12345678"))},
{"Invalid time", base64.URLEncoding.EncodeToString([]byte("foobar:foobar"))},
}
for _, bdt := range badDataTests {
assert.False(t, validTokenAtTime(bdt.tok, key, userID, actionID, oneMinuteFromNow))
}
})
}

View file

@ -2,11 +2,15 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"gitea.com/macaron/binding" "net/http"
"gitea.com/macaron/macaron"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"gitea.com/go-chi/binding"
) )
// AdminCreateUserForm form for admin to create user // AdminCreateUserForm form for admin to create user
@ -21,8 +25,9 @@ type AdminCreateUserForm struct {
} }
// Validate validates form fields // Validate validates form fields
func (f *AdminCreateUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AdminCreateUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AdminEditUserForm form for admin to create user // AdminEditUserForm form for admin to create user
@ -47,8 +52,9 @@ type AdminEditUserForm struct {
} }
// Validate validates form fields // Validate validates form fields
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AdminEditUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AdminDashboardForm form for admin dashboard operations // AdminDashboardForm form for admin dashboard operations
@ -58,6 +64,7 @@ type AdminDashboardForm struct {
} }
// Validate validates form fields // Validate validates form fields
func (f *AdminDashboardForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AdminDashboardForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -2,11 +2,15 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"gitea.com/macaron/binding" "net/http"
"gitea.com/macaron/macaron"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"gitea.com/go-chi/binding"
) )
// AuthenticationForm form for authentication // AuthenticationForm form for authentication
@ -65,6 +69,7 @@ type AuthenticationForm struct {
} }
// Validate validates fields // Validate validates fields
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AuthenticationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -3,14 +3,17 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"net/http"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"gitea.com/macaron/macaron"
) )
// ________ .__ __ .__ // ________ .__ __ .__
@ -28,8 +31,9 @@ type CreateOrgForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// UpdateOrgSettingForm form for updating organization settings // UpdateOrgSettingForm form for updating organization settings
@ -45,8 +49,9 @@ type UpdateOrgSettingForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ___________ // ___________
@ -67,6 +72,7 @@ type CreateTeamForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateTeamForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -2,11 +2,15 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"gitea.com/macaron/binding" "net/http"
"gitea.com/macaron/macaron"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"gitea.com/go-chi/binding"
) )
// NewBranchForm form for creating a new branch // NewBranchForm form for creating a new branch
@ -15,6 +19,7 @@ type NewBranchForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -3,21 +3,23 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"net/http"
"net/url" "net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/routers/utils"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"gitea.com/macaron/macaron"
) )
// _______________________________________ _________.______________________ _______________.___. // _______________________________________ _________.______________________ _______________.___.
@ -52,8 +54,9 @@ type CreateRepoForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// MigrateRepoForm form for migrating repository // MigrateRepoForm form for migrating repository
@ -82,8 +85,9 @@ type MigrateRepoForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ParseRemoteAddr checks if given remote address is valid, // ParseRemoteAddr checks if given remote address is valid,
@ -166,8 +170,9 @@ type RepoSettingForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// __________ .__ // __________ .__
@ -202,8 +207,9 @@ type ProtectBranchForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// __ __ ___. .__ .__ __ // __ __ ___. .__ .__ __
@ -263,8 +269,9 @@ type NewWebhookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewGogshookForm form for creating gogs hook // NewGogshookForm form for creating gogs hook
@ -276,8 +283,9 @@ type NewGogshookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewGogshookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewSlackHookForm form for creating slack hook // NewSlackHookForm form for creating slack hook
@ -291,8 +299,9 @@ type NewSlackHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// HasInvalidChannel validates the channel name is in the right format // HasInvalidChannel validates the channel name is in the right format
@ -309,8 +318,9 @@ type NewDiscordHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewDiscordHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewDingtalkHookForm form for creating dingtalk hook // NewDingtalkHookForm form for creating dingtalk hook
@ -320,8 +330,9 @@ type NewDingtalkHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewTelegramHookForm form for creating telegram hook // NewTelegramHookForm form for creating telegram hook
@ -332,8 +343,9 @@ type NewTelegramHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewTelegramHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewMatrixHookForm form for creating Matrix hook // NewMatrixHookForm form for creating Matrix hook
@ -346,8 +358,9 @@ type NewMatrixHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewMatrixHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewMatrixHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewMSTeamsHookForm form for creating MS Teams hook // NewMSTeamsHookForm form for creating MS Teams hook
@ -357,8 +370,9 @@ type NewMSTeamsHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewMSTeamsHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewFeishuHookForm form for creating feishu hook // NewFeishuHookForm form for creating feishu hook
@ -368,8 +382,9 @@ type NewFeishuHookForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewFeishuHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// .___ // .___
@ -393,8 +408,9 @@ type CreateIssueForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateIssueForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// CreateCommentForm form for creating comment // CreateCommentForm form for creating comment
@ -405,8 +421,9 @@ type CreateCommentForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ReactionForm form for adding and removing reaction // ReactionForm form for adding and removing reaction
@ -415,8 +432,9 @@ type ReactionForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *ReactionForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// IssueLockForm form for locking an issue // IssueLockForm form for locking an issue
@ -425,8 +443,9 @@ type IssueLockForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (i *IssueLockForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (i *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, i, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, i, ctx.Locale)
} }
// HasValidReason checks to make sure that the reason submitted in // HasValidReason checks to make sure that the reason submitted in
@ -489,8 +508,9 @@ type CreateMilestoneForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// .____ ___. .__ // .____ ___. .__
@ -509,8 +529,9 @@ type CreateLabelForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CreateLabelForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// InitializeLabelsForm form for initializing labels // InitializeLabelsForm form for initializing labels
@ -519,8 +540,9 @@ type InitializeLabelsForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// __________ .__ .__ __________ __ // __________ .__ .__ __________ __
@ -542,8 +564,9 @@ type MergePullRequestForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// CodeCommentForm form for adding code comments for PRs // CodeCommentForm form for adding code comments for PRs
@ -559,8 +582,9 @@ type CodeCommentForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// SubmitReviewForm for submitting a finished code review // SubmitReviewForm for submitting a finished code review
@ -571,8 +595,9 @@ type SubmitReviewForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *SubmitReviewForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ReviewType will return the corresponding reviewtype for type // ReviewType will return the corresponding reviewtype for type
@ -616,8 +641,9 @@ type NewReleaseForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// EditReleaseForm form for changing release // EditReleaseForm form for changing release
@ -630,8 +656,9 @@ type EditReleaseForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *EditReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// __ __.__ __ .__ // __ __.__ __ .__
@ -650,8 +677,9 @@ type NewWikiForm struct {
// Validate validates the fields // Validate validates the fields
// FIXME: use code generation to generate this method. // FIXME: use code generation to generate this method.
func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ___________ .___.__ __ // ___________ .___.__ __
@ -673,8 +701,9 @@ type EditRepoFileForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *EditRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// EditPreviewDiffForm form for changing preview diff // EditPreviewDiffForm form for changing preview diff
@ -683,8 +712,9 @@ type EditPreviewDiffForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ____ ___ .__ .___ // ____ ___ .__ .___
@ -706,8 +736,9 @@ type UploadRepoFileForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *UploadRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// RemoveUploadFileForm form for removing uploaded file // RemoveUploadFileForm form for removing uploaded file
@ -716,8 +747,9 @@ type RemoveUploadFileForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *RemoveUploadFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ________ .__ __ // ________ .__ __
@ -737,8 +769,9 @@ type DeleteRepoFileForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ___________.__ ___________ __ // ___________.__ ___________ __
@ -755,8 +788,9 @@ type AddTimeManuallyForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AddTimeManuallyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// SaveTopicForm form for save topics for repository // SaveTopicForm form for save topics for repository
@ -770,6 +804,7 @@ type DeadlineForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"testing" "testing"

View file

@ -3,16 +3,18 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"mime/multipart" "mime/multipart"
"net/http"
"strings" "strings"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"gitea.com/macaron/macaron"
) )
// InstallForm form for installation page // InstallForm form for installation page
@ -65,8 +67,9 @@ type InstallForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// _____ ____ _________________ ___ // _____ ____ _________________ ___
@ -87,8 +90,9 @@ type RegisterForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// IsEmailDomainWhitelisted validates that the email address // IsEmailDomainWhitelisted validates that the email address
@ -124,8 +128,9 @@ type MustChangePasswordForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *MustChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// SignInForm form for signing in with user/password // SignInForm form for signing in with user/password
@ -137,8 +142,9 @@ type SignInForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *SignInForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AuthorizationForm form for authorizing oauth2 clients // AuthorizationForm form for authorizing oauth2 clients
@ -156,8 +162,9 @@ type AuthorizationForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AuthorizationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// GrantApplicationForm form for authorizing oauth2 clients // GrantApplicationForm form for authorizing oauth2 clients
@ -170,8 +177,9 @@ type GrantApplicationForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *GrantApplicationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *GrantApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens // AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
@ -188,8 +196,9 @@ type AccessTokenForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// __________________________________________.___ _______ ________ _________ // __________________________________________.___ _______ ________ _________
@ -212,8 +221,9 @@ type UpdateProfileForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// Avatar types // Avatar types
@ -231,8 +241,9 @@ type AvatarForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AvatarForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AddEmailForm form for adding new email // AddEmailForm form for adding new email
@ -241,8 +252,9 @@ type AddEmailForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// UpdateThemeForm form for updating a users' theme // UpdateThemeForm form for updating a users' theme
@ -251,8 +263,9 @@ type UpdateThemeForm struct {
} }
// Validate validates the field // Validate validates the field
func (f *UpdateThemeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// IsThemeExists checks if the theme is a theme available in the config. // IsThemeExists checks if the theme is a theme available in the config.
@ -277,8 +290,9 @@ type ChangePasswordForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *ChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AddOpenIDForm is for changing openid uri // AddOpenIDForm is for changing openid uri
@ -287,8 +301,9 @@ type AddOpenIDForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AddOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AddOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// AddKeyForm form for adding SSH/GPG key // AddKeyForm form for adding SSH/GPG key
@ -300,8 +315,9 @@ type AddKeyForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *AddKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// NewAccessTokenForm form for creating access token // NewAccessTokenForm form for creating access token
@ -310,8 +326,9 @@ type NewAccessTokenForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *NewAccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// EditOAuth2ApplicationForm form for editing oauth2 applications // EditOAuth2ApplicationForm form for editing oauth2 applications
@ -321,8 +338,9 @@ type EditOAuth2ApplicationForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *EditOAuth2ApplicationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// TwoFactorAuthForm for logging in with 2FA token. // TwoFactorAuthForm for logging in with 2FA token.
@ -331,8 +349,9 @@ type TwoFactorAuthForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *TwoFactorAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *TwoFactorAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// TwoFactorScratchAuthForm for logging in with 2FA scratch token. // TwoFactorScratchAuthForm for logging in with 2FA scratch token.
@ -341,8 +360,9 @@ type TwoFactorScratchAuthForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *TwoFactorScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *TwoFactorScratchAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// U2FRegistrationForm for reserving an U2F name // U2FRegistrationForm for reserving an U2F name
@ -351,8 +371,9 @@ type U2FRegistrationForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *U2FRegistrationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *U2FRegistrationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// U2FDeleteForm for deleting U2F keys // U2FDeleteForm for deleting U2F keys
@ -361,6 +382,7 @@ type U2FDeleteForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *U2FDeleteForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -2,11 +2,14 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"gitea.com/macaron/binding" "net/http"
"gitea.com/macaron/macaron"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"gitea.com/go-chi/binding"
) )
// SignInOpenIDForm form for signing in with OpenID // SignInOpenIDForm form for signing in with OpenID
@ -16,8 +19,9 @@ type SignInOpenIDForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// SignUpOpenIDForm form for signin up with OpenID // SignUpOpenIDForm form for signin up with OpenID
@ -29,8 +33,9 @@ type SignUpOpenIDForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *SignUpOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *SignUpOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }
// ConnectOpenIDForm form for connecting an existing account to an OpenID URI // ConnectOpenIDForm form for connecting an existing account to an OpenID URI
@ -40,6 +45,7 @@ type ConnectOpenIDForm struct {
} }
// Validate validates the fields // Validate validates the fields
func (f *ConnectOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *ConnectOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale) ctx := context.GetContext(req)
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
} }

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package forms
import ( import (
"testing" "testing"

View file

@ -182,7 +182,7 @@ func PostLockHandler(ctx *context.Context) {
} }
var req api.LFSLockRequest var req api.LFSLockRequest
bodyReader := ctx.Req.Body().ReadCloser() bodyReader := ctx.Req.Body
defer bodyReader.Close() defer bodyReader.Close()
dec := json.NewDecoder(bodyReader) dec := json.NewDecoder(bodyReader)
if err := dec.Decode(&req); err != nil { if err := dec.Decode(&req); err != nil {
@ -317,7 +317,7 @@ func UnLockHandler(ctx *context.Context) {
} }
var req api.LFSLockDeleteRequest var req api.LFSLockDeleteRequest
bodyReader := ctx.Req.Body().ReadCloser() bodyReader := ctx.Req.Body
defer bodyReader.Close() defer bodyReader.Close()
dec := json.NewDecoder(bodyReader) dec := json.NewDecoder(bodyReader)
if err := dec.Decode(&req); err != nil { if err := dec.Decode(&req); err != nil {

View file

@ -22,7 +22,6 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"gitea.com/macaron/macaron"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
) )
@ -413,8 +412,8 @@ func PutHandler(ctx *context.Context) {
} }
contentStore := &ContentStore{ObjectStorage: storage.LFS} contentStore := &ContentStore{ObjectStorage: storage.LFS}
defer ctx.Req.Request.Body.Close() defer ctx.Req.Body.Close()
if err := contentStore.Put(meta, ctx.Req.Request.Body); err != nil { if err := contentStore.Put(meta, ctx.Req.Body); err != nil {
// Put will log the error itself // Put will log the error itself
ctx.Resp.WriteHeader(500) ctx.Resp.WriteHeader(500)
if err == errSizeMismatch || err == errHashMismatch { if err == errSizeMismatch || err == errHashMismatch {
@ -513,7 +512,7 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain // MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the metaMediaType // an Accept header with the metaMediaType
func MetaMatcher(r macaron.Request) bool { func MetaMatcher(r *http.Request) bool {
mediaParts := strings.Split(r.Header.Get("Accept"), ";") mediaParts := strings.Split(r.Header.Get("Accept"), ";")
mt := mediaParts[0] mt := mediaParts[0]
return mt == metaMediaType return mt == metaMediaType
@ -530,7 +529,7 @@ func unpack(ctx *context.Context) *RequestVars {
if r.Method == "POST" { // Maybe also check if +json if r.Method == "POST" { // Maybe also check if +json
var p RequestVars var p RequestVars
bodyReader := r.Body().ReadCloser() bodyReader := r.Body
defer bodyReader.Close() defer bodyReader.Close()
dec := json.NewDecoder(bodyReader) dec := json.NewDecoder(bodyReader)
err := dec.Decode(&p) err := dec.Decode(&p)
@ -553,7 +552,7 @@ func unpackbatch(ctx *context.Context) *BatchVars {
r := ctx.Req r := ctx.Req
var bv BatchVars var bv BatchVars
bodyReader := r.Body().ReadCloser() bodyReader := r.Body
defer bodyReader.Close() defer bodyReader.Close()
dec := json.NewDecoder(bodyReader) dec := json.NewDecoder(bodyReader)
err := dec.Decode(&bv) err := dec.Decode(&bv)
@ -586,7 +585,7 @@ func writeStatus(ctx *context.Context, status int) {
logRequest(ctx.Req, status) logRequest(ctx.Req, status)
} }
func logRequest(r macaron.Request, status int) { func logRequest(r *http.Request, status int) {
log.Debug("LFS request - Method: %s, URL: %s, Status %d", r.Method, r.URL, status) log.Debug("LFS request - Method: %s, URL: %s, Status %d", r.Method, r.URL, status)
} }

View file

@ -3,24 +3,19 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package auth package middlewares
import ( import (
"reflect" "reflect"
"strings" "strings"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/validation"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"gitea.com/macaron/macaron"
"github.com/unknwon/com" "github.com/unknwon/com"
) )
// IsAPIPath if URL is an api path
func IsAPIPath(url string) bool {
return strings.HasPrefix(url, "/api/")
}
// Form form binding interface // Form form binding interface
type Form interface { type Form interface {
binding.Validator binding.Validator
@ -35,7 +30,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
typ := reflect.TypeOf(form) typ := reflect.TypeOf(form)
val := reflect.ValueOf(form) val := reflect.ValueOf(form)
if typ.Kind() == reflect.Ptr { for typ.Kind() == reflect.Ptr {
typ = typ.Elem() typ = typ.Elem()
val = val.Elem() val = val.Elem()
} }
@ -84,7 +79,8 @@ func GetInclude(field reflect.StructField) string {
return getRuleBody(field, "Include(") return getRuleBody(field, "Include(")
} }
func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors { // Validate validate TODO:
func Validate(errs binding.Errors, data map[string]interface{}, f Form, l translation.Locale) binding.Errors {
if errs.Len() == 0 { if errs.Len() == 0 {
return errs return errs
} }

View file

@ -1,3 +1,4 @@
// Copyright 2020 The Macaron Authors
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -12,6 +13,56 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
// MaxAge sets the maximum age for a provided cookie
func MaxAge(maxAge int) func(*http.Cookie) {
return func(c *http.Cookie) {
c.MaxAge = maxAge
}
}
// Path sets the path for a provided cookie
func Path(path string) func(*http.Cookie) {
return func(c *http.Cookie) {
c.Path = path
}
}
// Domain sets the domain for a provided cookie
func Domain(domain string) func(*http.Cookie) {
return func(c *http.Cookie) {
c.Domain = domain
}
}
// Secure sets the secure setting for a provided cookie
func Secure(secure bool) func(*http.Cookie) {
return func(c *http.Cookie) {
c.Secure = secure
}
}
// HTTPOnly sets the HttpOnly setting for a provided cookie
func HTTPOnly(httpOnly bool) func(*http.Cookie) {
return func(c *http.Cookie) {
c.HttpOnly = httpOnly
}
}
// Expires sets the expires and rawexpires for a provided cookie
func Expires(expires time.Time) func(*http.Cookie) {
return func(c *http.Cookie) {
c.Expires = expires
c.RawExpires = expires.Format(time.UnixDate)
}
}
// SameSite sets the SameSite for a provided cookie
func SameSite(sameSite http.SameSite) func(*http.Cookie) {
return func(c *http.Cookie) {
c.SameSite = sameSite
}
}
// NewCookie creates a cookie // NewCookie creates a cookie
func NewCookie(name, value string, maxAge int) *http.Cookie { func NewCookie(name, value string, maxAge int) *http.Cookie {
return &http.Cookie{ return &http.Cookie{
@ -102,3 +153,13 @@ func SetCookie(resp http.ResponseWriter, name string, value string, others ...in
resp.Header().Add("Set-Cookie", cookie.String()) resp.Header().Add("Set-Cookie", cookie.String())
} }
// GetCookie returns given cookie value from request header.
func GetCookie(req *http.Request, name string) string {
cookie, err := req.Cookie(name)
if err != nil {
return ""
}
val, _ := url.QueryUnescape(cookie.Value)
return val
}

View file

@ -0,0 +1,10 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package middlewares
// DataStore represents a data store
type DataStore interface {
GetData() map[string]interface{}
}

View file

@ -0,0 +1,65 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package middlewares
import "net/url"
// flashes enumerates all the flash types
const (
SuccessFlash = "SuccessMsg"
ErrorFlash = "ErrorMsg"
WarnFlash = "WarningMsg"
InfoFlash = "InfoMsg"
)
var (
// FlashNow FIXME:
FlashNow bool
)
// Flash represents a one time data transfer between two requests.
type Flash struct {
DataStore
url.Values
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
}
func (f *Flash) set(name, msg string, current ...bool) {
isShow := false
if (len(current) == 0 && FlashNow) ||
(len(current) > 0 && current[0]) {
isShow = true
}
if isShow {
f.GetData()["Flash"] = f
} else {
f.Set(name, msg)
}
}
// Error sets error message
func (f *Flash) Error(msg string, current ...bool) {
f.ErrorMsg = msg
f.set("error", msg, current...)
}
// Warning sets warning message
func (f *Flash) Warning(msg string, current ...bool) {
f.WarningMsg = msg
f.set("warning", msg, current...)
}
// Info sets info message
func (f *Flash) Info(msg string, current ...bool) {
f.InfoMsg = msg
f.set("info", msg, current...)
}
// Success sets success message
func (f *Flash) Success(msg string, current ...bool) {
f.SuccessMsg = msg
f.set("success", msg, current...)
}

View file

@ -23,12 +23,14 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale {
// 2. Get language information from cookies. // 2. Get language information from cookies.
if len(lang) == 0 { if len(lang) == 0 {
ck, _ := req.Cookie("lang") ck, _ := req.Cookie("lang")
lang = ck.Value if ck != nil {
hasCookie = true lang = ck.Value
hasCookie = true
}
} }
// Check again in case someone modify by purpose. // Check again in case someone modify by purpose.
if !i18n.IsExist(lang) { if lang != "" && !i18n.IsExist(lang) {
lang = "" lang = ""
hasCookie = false hasCookie = false
} }

View file

@ -1,217 +0,0 @@
// Copyright 2013 Beego Authors
// Copyright 2014 The Macaron Authors
// Copyright 2020 The Gitea Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package middlewares
import (
"fmt"
"sync"
"time"
"code.gitea.io/gitea/modules/nosql"
"gitea.com/go-chi/session"
"github.com/go-redis/redis/v7"
)
// RedisStore represents a redis session store implementation.
// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
type RedisStore struct {
c redis.UniversalClient
prefix, sid string
duration time.Duration
lock sync.RWMutex
data map[interface{}]interface{}
}
// NewRedisStore creates and returns a redis session store.
func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
return &RedisStore{
c: c,
prefix: prefix,
sid: sid,
duration: dur,
data: kv,
}
}
// Set sets value to given key in session.
func (s *RedisStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
s.data[key] = val
return nil
}
// Get gets value by given key in session.
func (s *RedisStore) Get(key interface{}) interface{} {
s.lock.RLock()
defer s.lock.RUnlock()
return s.data[key]
}
// Delete delete a key from session.
func (s *RedisStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.data, key)
return nil
}
// ID returns current session ID.
func (s *RedisStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (s *RedisStore) Release() error {
// Skip encoding if the data is empty
if len(s.data) == 0 {
return nil
}
data, err := session.EncodeGob(s.data)
if err != nil {
return err
}
return s.c.Set(s.prefix+s.sid, string(data), s.duration).Err()
}
// Flush deletes all session data.
func (s *RedisStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}
// RedisProvider represents a redis session provider implementation.
type RedisProvider struct {
c redis.UniversalClient
duration time.Duration
prefix string
}
// Init initializes redis session provider.
// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session;
func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime))
if err != nil {
return err
}
uri := nosql.ToRedisURI(configs)
for k, v := range uri.Query() {
switch k {
case "prefix":
p.prefix = v[0]
}
}
p.c = nosql.GetManager().GetRedisClient(uri.String())
return p.c.Ping().Err()
}
// Read returns raw session store by session ID.
func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
psid := p.prefix + sid
if !p.Exist(sid) {
if err := p.c.Set(psid, "", p.duration).Err(); err != nil {
return nil, err
}
}
var kv map[interface{}]interface{}
kvs, err := p.c.Get(psid).Result()
if err != nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob([]byte(kvs))
if err != nil {
return nil, err
}
}
return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
}
// Exist returns true if session with given ID exists.
func (p *RedisProvider) Exist(sid string) bool {
v, err := p.c.Exists(p.prefix + sid).Result()
return err == nil && v == 1
}
// Destroy deletes a session by session ID.
func (p *RedisProvider) Destroy(sid string) error {
return p.c.Del(p.prefix + sid).Err()
}
// Regenerate regenerates a session store from old session ID to new one.
func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
poldsid := p.prefix + oldsid
psid := p.prefix + sid
if p.Exist(sid) {
return nil, fmt.Errorf("new sid '%s' already exists", sid)
} else if !p.Exist(oldsid) {
// Make a fake old session.
if err = p.c.Set(poldsid, "", p.duration).Err(); err != nil {
return nil, err
}
}
if err = p.c.Rename(poldsid, psid).Err(); err != nil {
return nil, err
}
var kv map[interface{}]interface{}
kvs, err := p.c.Get(psid).Result()
if err != nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob([]byte(kvs))
if err != nil {
return nil, err
}
}
return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
}
// Count counts and returns number of sessions.
func (p *RedisProvider) Count() int {
return int(p.c.DBSize().Val())
}
// GC calls GC to clean expired sessions.
func (*RedisProvider) GC() {}
func init() {
session.Register("redis", &RedisProvider{})
}

View file

@ -1,196 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package middlewares
import (
"encoding/json"
"fmt"
"sync"
"gitea.com/go-chi/session"
couchbase "gitea.com/go-chi/session/couchbase"
memcache "gitea.com/go-chi/session/memcache"
mysql "gitea.com/go-chi/session/mysql"
postgres "gitea.com/go-chi/session/postgres"
)
// VirtualSessionProvider represents a shadowed session provider implementation.
// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
type VirtualSessionProvider struct {
lock sync.RWMutex
provider session.Provider
}
// Init initializes the cookie session provider with given root path.
func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
var opts session.Options
if err := json.Unmarshal([]byte(config), &opts); err != nil {
return err
}
// Note that these options are unprepared so we can't just use NewManager here.
// Nor can we access the provider map in session.
// So we will just have to do this by hand.
// This is only slightly more wrong than modules/setting/session.go:23
switch opts.Provider {
case "memory":
o.provider = &session.MemProvider{}
case "file":
o.provider = &session.FileProvider{}
case "redis":
o.provider = &RedisProvider{}
case "mysql":
o.provider = &mysql.MysqlProvider{}
case "postgres":
o.provider = &postgres.PostgresProvider{}
case "couchbase":
o.provider = &couchbase.CouchbaseProvider{}
case "memcache":
o.provider = &memcache.MemcacheProvider{}
default:
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
}
return o.provider.Init(gclifetime, opts.ProviderConfig)
}
// Read returns raw session store by session ID.
func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
o.lock.RLock()
defer o.lock.RUnlock()
if o.provider.Exist(sid) {
return o.provider.Read(sid)
}
kv := make(map[interface{}]interface{})
kv["_old_uid"] = "0"
return NewVirtualStore(o, sid, kv), nil
}
// Exist returns true if session with given ID exists.
func (o *VirtualSessionProvider) Exist(sid string) bool {
return true
}
// Destroy deletes a session by session ID.
func (o *VirtualSessionProvider) Destroy(sid string) error {
o.lock.Lock()
defer o.lock.Unlock()
return o.provider.Destroy(sid)
}
// Regenerate regenerates a session store from old session ID to new one.
func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
o.lock.Lock()
defer o.lock.Unlock()
return o.provider.Regenerate(oldsid, sid)
}
// Count counts and returns number of sessions.
func (o *VirtualSessionProvider) Count() int {
o.lock.RLock()
defer o.lock.RUnlock()
return o.provider.Count()
}
// GC calls GC to clean expired sessions.
func (o *VirtualSessionProvider) GC() {
o.provider.GC()
}
func init() {
session.Register("VirtualSession", &VirtualSessionProvider{})
}
// VirtualStore represents a virtual session store implementation.
type VirtualStore struct {
p *VirtualSessionProvider
sid string
lock sync.RWMutex
data map[interface{}]interface{}
released bool
}
// NewVirtualStore creates and returns a virtual session store.
func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
return &VirtualStore{
p: p,
sid: sid,
data: kv,
}
}
// Set sets value to given key in session.
func (s *VirtualStore) Set(key, val interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
s.data[key] = val
return nil
}
// Get gets value by given key in session.
func (s *VirtualStore) Get(key interface{}) interface{} {
s.lock.RLock()
defer s.lock.RUnlock()
return s.data[key]
}
// Delete delete a key from session.
func (s *VirtualStore) Delete(key interface{}) error {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.data, key)
return nil
}
// ID returns current session ID.
func (s *VirtualStore) ID() string {
return s.sid
}
// Release releases resource and save data to provider.
func (s *VirtualStore) Release() error {
s.lock.Lock()
defer s.lock.Unlock()
// Now need to lock the provider
s.p.lock.Lock()
defer s.p.lock.Unlock()
if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
// Now ensure that we don't exist!
realProvider := s.p.provider
if !s.released && realProvider.Exist(s.sid) {
// This is an error!
return fmt.Errorf("new sid '%s' already exists", s.sid)
}
realStore, err := realProvider.Read(s.sid)
if err != nil {
return err
}
if err := realStore.Flush(); err != nil {
return err
}
for key, value := range s.data {
if err := realStore.Set(key, value); err != nil {
return err
}
}
err = realStore.Release()
if err == nil {
s.released = true
}
return err
}
return nil
}
// Flush deletes all session data.
func (s *VirtualStore) Flush() error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = make(map[interface{}]interface{})
return nil
}

View file

@ -23,7 +23,7 @@ import (
"code.gitea.io/gitea/modules/nosql" "code.gitea.io/gitea/modules/nosql"
"gitea.com/macaron/session" "gitea.com/go-chi/session"
"github.com/go-redis/redis/v7" "github.com/go-redis/redis/v7"
) )

12
modules/session/store.go Normal file
View file

@ -0,0 +1,12 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package session
// Store represents a session store
type Store interface {
Get(interface{}) interface{}
Set(interface{}, interface{}) error
Delete(interface{}) error
}

View file

@ -9,12 +9,11 @@ import (
"fmt" "fmt"
"sync" "sync"
"gitea.com/macaron/session" "gitea.com/go-chi/session"
couchbase "gitea.com/macaron/session/couchbase" couchbase "gitea.com/go-chi/session/couchbase"
memcache "gitea.com/macaron/session/memcache" memcache "gitea.com/go-chi/session/memcache"
mysql "gitea.com/macaron/session/mysql" mysql "gitea.com/go-chi/session/mysql"
nodb "gitea.com/macaron/session/nodb" postgres "gitea.com/go-chi/session/postgres"
postgres "gitea.com/macaron/session/postgres"
) )
// VirtualSessionProvider represents a shadowed session provider implementation. // VirtualSessionProvider represents a shadowed session provider implementation.
@ -48,8 +47,6 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
o.provider = &couchbase.CouchbaseProvider{} o.provider = &couchbase.CouchbaseProvider{}
case "memcache": case "memcache":
o.provider = &memcache.MemcacheProvider{} o.provider = &memcache.MemcacheProvider{}
case "nodb":
o.provider = &nodb.NodbProvider{}
default: default:
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider) return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
} }

View file

@ -254,17 +254,6 @@ func generateNamedLogger(key string, options defaultLogOptions) *LogDescription
return &description return &description
} }
func newMacaronLogService() {
options := newDefaultLogOptions()
options.filename = filepath.Join(LogRootPath, "macaron.log")
options.bufferLength = Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000)
Cfg.Section("log").Key("MACARON").MustString("file")
if RedirectMacaronLog {
generateNamedLogger("macaron", options)
}
}
func newAccessLogService() { func newAccessLogService() {
EnableAccessLog = Cfg.Section("log").Key("ENABLE_ACCESS_LOG").MustBool(false) EnableAccessLog = Cfg.Section("log").Key("ENABLE_ACCESS_LOG").MustBool(false)
AccessLogTemplate = Cfg.Section("log").Key("ACCESS_LOG_TEMPLATE").MustString( AccessLogTemplate = Cfg.Section("log").Key("ACCESS_LOG_TEMPLATE").MustString(
@ -360,7 +349,6 @@ func RestartLogsWithPIDSuffix() {
// NewLogServices creates all the log services // NewLogServices creates all the log services
func NewLogServices(disableConsole bool) { func NewLogServices(disableConsole bool) {
newLogService() newLogService()
newMacaronLogService()
newRouterLogService() newRouterLogService()
newAccessLogService() newAccessLogService()
NewXORMLogService(disableConsole) NewXORMLogService(disableConsole)

View file

@ -41,7 +41,7 @@ var (
func newSessionService() { func newSessionService() {
sec := Cfg.Section("session") sec := Cfg.Section("session")
SessionConfig.Provider = sec.Key("PROVIDER").In("memory", SessionConfig.Provider = sec.Key("PROVIDER").In("memory",
[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "nodb"}) []string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache"})
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ") SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) { if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig) SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)

View file

@ -12,6 +12,8 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/unrolled/render"
) )
// Vars represents variables to be render in golang templates // Vars represents variables to be render in golang templates
@ -80,3 +82,15 @@ func getDirAssetNames(dir string) []string {
} }
return tmpls return tmpls
} }
// HTMLRenderer returns a render.
func HTMLRenderer() *render.Render {
return render.New(render.Options{
Extensions: []string{".tmpl"},
Directory: "templates",
Funcs: NewFuncMap(),
Asset: GetAsset,
AssetNames: GetAssetNames,
IsDevelopment: !setting.IsProd(),
})
}

View file

@ -18,8 +18,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
) )
var ( var (
@ -46,29 +44,6 @@ func GetAssetNames() []string {
return append(tmpls, tmpls2...) return append(tmpls, tmpls2...)
} }
// HTMLRenderer implements the macaron handler for serving HTML templates.
func HTMLRenderer() macaron.Handler {
return macaron.Renderer(macaron.RenderOptions{
Funcs: NewFuncMap(),
Directory: path.Join(setting.StaticRootPath, "templates"),
AppendDirectories: []string{
path.Join(setting.CustomPath, "templates"),
},
})
}
// JSONRenderer implements the macaron handler for serving JSON templates.
func JSONRenderer() macaron.Handler {
return macaron.Renderer(macaron.RenderOptions{
Funcs: NewFuncMap(),
Directory: path.Join(setting.StaticRootPath, "templates"),
AppendDirectories: []string{
path.Join(setting.CustomPath, "templates"),
},
HTMLContentType: "application/json",
})
}
// Mailer provides the templates required for sending notification mails. // Mailer provides the templates required for sending notification mails.
func Mailer() (*texttmpl.Template, *template.Template) { func Mailer() (*texttmpl.Template, *template.Template) {
for _, funcs := range NewTextFuncMap() { for _, funcs := range NewTextFuncMap() {

View file

@ -7,10 +7,7 @@
package templates package templates
import ( import (
"bytes"
"fmt"
"html/template" "html/template"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
@ -21,8 +18,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
) )
var ( var (
@ -30,24 +25,6 @@ var (
bodyTemplates = template.New("") bodyTemplates = template.New("")
) )
type templateFileSystem struct {
files []macaron.TemplateFile
}
func (templates templateFileSystem) ListFiles() []macaron.TemplateFile {
return templates.files
}
func (templates templateFileSystem) Get(name string) (io.Reader, error) {
for i := range templates.files {
if templates.files[i].Name()+templates.files[i].Ext() == name {
return bytes.NewReader(templates.files[i].Data()), nil
}
}
return nil, fmt.Errorf("file '%s' not found", name)
}
// GetAsset get a special asset, only for chi // GetAsset get a special asset, only for chi
func GetAsset(name string) ([]byte, error) { func GetAsset(name string) ([]byte, error) {
bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name)) bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name))
@ -72,95 +49,6 @@ func GetAssetNames() []string {
return append(tmpls, customTmpls...) return append(tmpls, customTmpls...)
} }
func NewTemplateFileSystem() templateFileSystem {
fs := templateFileSystem{}
fs.files = make([]macaron.TemplateFile, 0, 10)
for _, assetPath := range AssetNames() {
if strings.HasPrefix(assetPath, "mail/") {
continue
}
if !strings.HasSuffix(assetPath, ".tmpl") {
continue
}
content, err := Asset(assetPath)
if err != nil {
log.Warn("Failed to read embedded %s template. %v", assetPath, err)
continue
}
fs.files = append(fs.files, macaron.NewTplFile(
strings.TrimSuffix(
assetPath,
".tmpl",
),
content,
".tmpl",
))
}
customDir := path.Join(setting.CustomPath, "templates")
isDir, err := util.IsDir(customDir)
if err != nil {
log.Warn("Unable to check if templates dir %s is a directory. Error: %v", customDir, err)
}
if isDir {
files, err := util.StatDir(customDir)
if err != nil {
log.Warn("Failed to read %s templates dir. %v", customDir, err)
} else {
for _, filePath := range files {
if strings.HasPrefix(filePath, "mail/") {
continue
}
if !strings.HasSuffix(filePath, ".tmpl") {
continue
}
content, err := ioutil.ReadFile(path.Join(customDir, filePath))
if err != nil {
log.Warn("Failed to read custom %s template. %v", filePath, err)
continue
}
fs.files = append(fs.files, macaron.NewTplFile(
strings.TrimSuffix(
filePath,
".tmpl",
),
content,
".tmpl",
))
}
}
}
return fs
}
// HTMLRenderer implements the macaron handler for serving HTML templates.
func HTMLRenderer() macaron.Handler {
return macaron.Renderer(macaron.RenderOptions{
Funcs: NewFuncMap(),
TemplateFileSystem: NewTemplateFileSystem(),
})
}
// JSONRenderer implements the macaron handler for serving JSON templates.
func JSONRenderer() macaron.Handler {
return macaron.Renderer(macaron.RenderOptions{
Funcs: NewFuncMap(),
TemplateFileSystem: NewTemplateFileSystem(),
HTMLContentType: "application/json",
})
}
// Mailer provides the templates required for sending notification mails. // Mailer provides the templates required for sending notification mails.
func Mailer() (*texttmpl.Template, *template.Template) { func Mailer() (*texttmpl.Template, *template.Template) {
for _, funcs := range NewTextFuncMap() { for _, funcs := range NewTextFuncMap() {

View file

@ -5,6 +5,9 @@
package test package test
import ( import (
scontext "context"
"html/template"
"io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -13,32 +16,37 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/middlewares"
"gitea.com/macaron/macaron" "github.com/go-chi/chi"
"gitea.com/macaron/session"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/unrolled/render"
) )
// MockContext mock context for unit tests // MockContext mock context for unit tests
func MockContext(t *testing.T, path string) *context.Context { func MockContext(t *testing.T, path string) *context.Context {
var macaronContext macaron.Context var resp = &mockResponseWriter{}
macaronContext.ReplaceAllParams(macaron.Params{}) var ctx = context.Context{
macaronContext.Locale = &mockLocale{} Render: &mockRender{},
requestURL, err := url.Parse(path) Data: make(map[string]interface{}),
assert.NoError(t, err) Flash: &middlewares.Flash{
macaronContext.Req = macaron.Request{Request: &http.Request{
URL: requestURL,
Form: url.Values{},
}}
macaronContext.Resp = &mockResponseWriter{}
macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp}
macaronContext.Data = map[string]interface{}{}
return &context.Context{
Context: &macaronContext,
Flash: &session.Flash{
Values: make(url.Values), Values: make(url.Values),
}, },
Resp: context.NewResponse(resp),
Locale: &mockLocale{},
} }
requestURL, err := url.Parse(path)
assert.NoError(t, err)
var req = &http.Request{
URL: requestURL,
Form: url.Values{},
}
chiCtx := chi.NewRouteContext()
req = req.WithContext(scontext.WithValue(req.Context(), chi.RouteCtxKey, chiCtx))
ctx.Req = context.WithContext(req, &ctx)
return &ctx
} }
// LoadRepo load a repo into a test context. // LoadRepo load a repo into a test context.
@ -113,77 +121,20 @@ func (rw *mockResponseWriter) Size() int {
return rw.size return rw.size
} }
func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) {
b(rw)
}
func (rw *mockResponseWriter) Push(target string, opts *http.PushOptions) error { func (rw *mockResponseWriter) Push(target string, opts *http.PushOptions) error {
return nil return nil
} }
type mockRender struct { type mockRender struct {
http.ResponseWriter
} }
func (tr *mockRender) SetResponseWriter(rw http.ResponseWriter) { func (tr *mockRender) TemplateLookup(tmpl string) *template.Template {
tr.ResponseWriter = rw return nil
} }
func (tr *mockRender) JSON(status int, _ interface{}) { func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ interface{}, _ ...render.HTMLOptions) error {
tr.Status(status) if resp, ok := w.(http.ResponseWriter); ok {
} resp.WriteHeader(status)
}
func (tr *mockRender) JSONString(interface{}) (string, error) { return nil
return "", nil
}
func (tr *mockRender) RawData(status int, _ []byte) {
tr.Status(status)
}
func (tr *mockRender) PlainText(status int, _ []byte) {
tr.Status(status)
}
func (tr *mockRender) HTML(status int, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
tr.Status(status)
}
func (tr *mockRender) HTMLSet(status int, _ string, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
tr.Status(status)
}
func (tr *mockRender) HTMLSetString(string, string, interface{}, ...macaron.HTMLOptions) (string, error) {
return "", nil
}
func (tr *mockRender) HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) {
return "", nil
}
func (tr *mockRender) HTMLSetBytes(string, string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
return nil, nil
}
func (tr *mockRender) HTMLBytes(string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
return nil, nil
}
func (tr *mockRender) XML(status int, _ interface{}) {
tr.Status(status)
}
func (tr *mockRender) Error(status int, _ ...string) {
tr.Status(status)
}
func (tr *mockRender) Status(status int) {
tr.ResponseWriter.WriteHeader(status)
}
func (tr *mockRender) SetTemplatePath(string, string) {
}
func (tr *mockRender) HasTemplateSet(string) bool {
return true
} }

View file

@ -10,8 +10,8 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
macaroni18n "gitea.com/macaron/i18n"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/unknwon/i18n" "github.com/unknwon/i18n"
) )
@ -27,13 +27,11 @@ const (
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
setting.StaticRootPath = "../../"
setting.Names = []string{"english"}
setting.Langs = []string{"en-US"}
// setup // setup
macaroni18n.I18n(macaroni18n.Options{ translation.InitLocales()
Directory: "../../options/locale/",
DefaultLang: "en-US",
Langs: []string{"en-US"},
Names: []string{"english"},
})
BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
// run the tests // run the tests

View file

@ -9,7 +9,6 @@ import (
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
macaron_i18n "gitea.com/macaron/i18n"
"github.com/unknwon/i18n" "github.com/unknwon/i18n"
"golang.org/x/text/language" "golang.org/x/text/language"
) )
@ -20,49 +19,57 @@ type Locale interface {
Tr(string, ...interface{}) string Tr(string, ...interface{}) string
} }
// LangType represents a lang type
type LangType struct {
Lang, Name string
}
var ( var (
matcher language.Matcher matcher language.Matcher
allLangs []LangType
) )
// AllLangs returns all supported langauages
func AllLangs() []LangType {
return allLangs
}
// InitLocales loads the locales // InitLocales loads the locales
func InitLocales() { func InitLocales() {
localeNames, err := options.Dir("locale") localeNames, err := options.Dir("locale")
if err != nil { if err != nil {
log.Fatal("Failed to list locale files: %v", err) log.Fatal("Failed to list locale files: %v", err)
} }
localFiles := make(map[string][]byte)
localFiles := make(map[string][]byte)
for _, name := range localeNames { for _, name := range localeNames {
localFiles[name], err = options.Locale(name) localFiles[name], err = options.Locale(name)
if err != nil { if err != nil {
log.Fatal("Failed to load %s locale file. %v", name, err) log.Fatal("Failed to load %s locale file. %v", name, err)
} }
} }
// These codes will be used once macaron removed // These codes will be used once macaron removed
/*tags := make([]language.Tag, len(setting.Langs)) tags := make([]language.Tag, len(setting.Langs))
for i, lang := range setting.Langs { for i, lang := range setting.Langs {
tags[i] = language.Raw.Make(lang) tags[i] = language.Raw.Make(lang)
} }
matcher = language.NewMatcher(tags)
for i, name := range setting.Names {
i18n.SetMessage(setting.Langs[i], localFiles[name])
}
i18n.SetDefaultLang("en-US")*/
// To be compatible with macaron, we now have to use macaron i18n, once macaron matcher = language.NewMatcher(tags)
// removed, we can use i18n directly for i := range setting.Names {
macaron_i18n.I18n(macaron_i18n.Options{ key := "locale_" + setting.Langs[i] + ".ini"
SubURL: setting.AppSubURL, if err := i18n.SetMessageWithDesc(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil {
Files: localFiles, log.Fatal("Failed to set messages to %s: %v", setting.Langs[i], err)
Langs: setting.Langs, }
Names: setting.Names, }
DefaultLang: "en-US", i18n.SetDefaultLang("en-US")
Redirect: false,
CookieDomain: setting.SessionConfig.Domain, allLangs = make([]LangType, 0, i18n.Count()-1)
}) langs := i18n.ListLangs()
names := i18n.ListLangDescs()
for i, v := range langs {
allLangs = append(allLangs, LangType{v, names[i]})
}
} }
// Match matches accept languages // Match matches accept languages

View file

@ -9,7 +9,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )

View file

@ -9,8 +9,8 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"gitea.com/macaron/macaron" "github.com/go-chi/chi"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -34,9 +34,10 @@ type (
func performValidationTest(t *testing.T, testCase validationTestCase) { func performValidationTest(t *testing.T, testCase validationTestCase) {
httpRecorder := httptest.NewRecorder() httpRecorder := httptest.NewRecorder()
m := macaron.Classic() m := chi.NewRouter()
m.Post(testRoute, binding.Validate(testCase.data), func(actual binding.Errors) { m.Post(testRoute, func(resp http.ResponseWriter, req *http.Request) {
actual := binding.Validate(req, testCase.data)
// see https://github.com/stretchr/testify/issues/435 // see https://github.com/stretchr/testify/issues/435
if actual == nil { if actual == nil {
actual = binding.Errors{} actual = binding.Errors{}
@ -49,7 +50,7 @@ func performValidationTest(t *testing.T, testCase validationTestCase) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
req.Header.Add("Content-Type", "x-www-form-urlencoded")
m.ServeHTTP(httpRecorder, req) m.ServeHTTP(httpRecorder, req)
switch httpRecorder.Code { switch httpRecorder.Code {

View file

@ -7,7 +7,7 @@ package validation
import ( import (
"testing" "testing"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )

View file

@ -7,7 +7,7 @@ package validation
import ( import (
"testing" "testing"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
) )
var gitRefNameValidationTestCases = []validationTestCase{ var gitRefNameValidationTestCases = []validationTestCase{

View file

@ -7,7 +7,7 @@ package validation
import ( import (
"testing" "testing"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
) )
var urlValidationTestCases = []validationTestCase{ var urlValidationTestCases = []validationTestCase{

322
modules/web/route.go Normal file
View file

@ -0,0 +1,322 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package web
import (
"fmt"
"net/http"
"reflect"
"strings"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/middlewares"
"gitea.com/go-chi/binding"
"github.com/go-chi/chi"
)
// Wrap converts all kinds of routes to standard library one
func Wrap(handlers ...interface{}) http.HandlerFunc {
if len(handlers) == 0 {
panic("No handlers found")
}
for _, handler := range handlers {
switch t := handler.(type) {
case http.HandlerFunc, func(http.ResponseWriter, *http.Request),
func(ctx *context.Context),
func(*context.APIContext),
func(*context.PrivateContext),
func(http.Handler) http.Handler:
default:
panic(fmt.Sprintf("Unsupported handler type: %#v", t))
}
}
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
for i := 0; i < len(handlers); i++ {
handler := handlers[i]
switch t := handler.(type) {
case http.HandlerFunc:
t(resp, req)
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
return
}
case func(http.ResponseWriter, *http.Request):
t(resp, req)
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
return
}
case func(ctx *context.Context):
ctx := context.GetContext(req)
t(ctx)
if ctx.Written() {
return
}
case func(*context.APIContext):
ctx := context.GetAPIContext(req)
t(ctx)
if ctx.Written() {
return
}
case func(*context.PrivateContext):
ctx := context.GetPrivateContext(req)
t(ctx)
if ctx.Written() {
return
}
case func(http.Handler) http.Handler:
var next = http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
t(next).ServeHTTP(resp, req)
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
return
}
default:
panic(fmt.Sprintf("Unsupported handler type: %#v", t))
}
}
})
}
// Middle wrap a context function as a chi middleware
func Middle(f func(ctx *context.Context)) func(netx http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := context.GetContext(req)
f(ctx)
if ctx.Written() {
return
}
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}
// MiddleAPI wrap a context function as a chi middleware
func MiddleAPI(f func(ctx *context.APIContext)) func(netx http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := context.GetAPIContext(req)
f(ctx)
if ctx.Written() {
return
}
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}
// Bind binding an obj to a handler
func Bind(obj interface{}) http.HandlerFunc {
var tp = reflect.TypeOf(obj)
if tp.Kind() == reflect.Ptr {
tp = tp.Elem()
}
if tp.Kind() != reflect.Struct {
panic("Only structs are allowed to bind")
}
return Wrap(func(ctx *context.Context) {
var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
binding.Bind(ctx.Req, theObj)
SetForm(ctx, theObj)
middlewares.AssignForm(theObj, ctx.Data)
})
}
// SetForm set the form object
func SetForm(data middlewares.DataStore, obj interface{}) {
data.GetData()["__form"] = obj
}
// GetForm returns the validate form information
func GetForm(data middlewares.DataStore) interface{} {
return data.GetData()["__form"]
}
// Route defines a route based on chi's router
type Route struct {
R chi.Router
curGroupPrefix string
curMiddlewares []interface{}
}
// NewRoute creates a new route
func NewRoute() *Route {
r := chi.NewRouter()
return &Route{
R: r,
curGroupPrefix: "",
curMiddlewares: []interface{}{},
}
}
// Use supports two middlewares
func (r *Route) Use(middlewares ...interface{}) {
if r.curGroupPrefix != "" {
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
} else {
for _, middle := range middlewares {
switch t := middle.(type) {
case func(http.Handler) http.Handler:
r.R.Use(t)
case func(*context.Context):
r.R.Use(Middle(t))
case func(*context.APIContext):
r.R.Use(MiddleAPI(t))
default:
panic(fmt.Sprintf("Unsupported middleware type: %#v", t))
}
}
}
}
// Group mounts a sub-Router along a `pattern` string.
func (r *Route) Group(pattern string, fn func(), middlewares ...interface{}) {
var previousGroupPrefix = r.curGroupPrefix
var previousMiddlewares = r.curMiddlewares
r.curGroupPrefix += pattern
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
fn()
r.curGroupPrefix = previousGroupPrefix
r.curMiddlewares = previousMiddlewares
}
func (r *Route) getPattern(pattern string) string {
newPattern := r.curGroupPrefix + pattern
if !strings.HasPrefix(newPattern, "/") {
newPattern = "/" + newPattern
}
if newPattern == "/" {
return newPattern
}
return strings.TrimSuffix(newPattern, "/")
}
// Mount attaches another Route along ./pattern/*
func (r *Route) Mount(pattern string, subR *Route) {
var middlewares = make([]interface{}, len(r.curMiddlewares))
copy(middlewares, r.curMiddlewares)
subR.Use(middlewares...)
r.R.Mount(r.getPattern(pattern), subR.R)
}
// Any delegate requests for all methods
func (r *Route) Any(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.HandleFunc(r.getPattern(pattern), Wrap(middlewares...))
}
// Route delegate special methods
func (r *Route) Route(pattern string, methods string, h ...interface{}) {
p := r.getPattern(pattern)
ms := strings.Split(methods, ",")
var middlewares = r.getMiddlewares(h)
for _, method := range ms {
r.R.MethodFunc(strings.TrimSpace(method), p, Wrap(middlewares...))
}
}
// Delete delegate delete method
func (r *Route) Delete(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.Delete(r.getPattern(pattern), Wrap(middlewares...))
}
func (r *Route) getMiddlewares(h []interface{}) []interface{} {
var middlewares = make([]interface{}, len(r.curMiddlewares), len(r.curMiddlewares)+len(h))
copy(middlewares, r.curMiddlewares)
middlewares = append(middlewares, h...)
return middlewares
}
// Get delegate get method
func (r *Route) Get(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
}
// Head delegate head method
func (r *Route) Head(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.Head(r.getPattern(pattern), Wrap(middlewares...))
}
// Post delegate post method
func (r *Route) Post(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
}
// Put delegate put method
func (r *Route) Put(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.Put(r.getPattern(pattern), Wrap(middlewares...))
}
// Patch delegate patch method
func (r *Route) Patch(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
r.R.Patch(r.getPattern(pattern), Wrap(middlewares...))
}
// ServeHTTP implements http.Handler
func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.R.ServeHTTP(w, req)
}
// NotFound defines a handler to respond whenever a route could
// not be found.
func (r *Route) NotFound(h http.HandlerFunc) {
r.R.NotFound(h)
}
// MethodNotAllowed defines a handler to respond whenever a method is
// not allowed.
func (r *Route) MethodNotAllowed(h http.HandlerFunc) {
r.R.MethodNotAllowed(h)
}
// Combo deletegate requests to Combo
func (r *Route) Combo(pattern string, h ...interface{}) *Combo {
return &Combo{r, pattern, h}
}
// Combo represents a tiny group routes with same pattern
type Combo struct {
r *Route
pattern string
h []interface{}
}
// Get deletegate Get method
func (c *Combo) Get(h ...interface{}) *Combo {
c.r.Get(c.pattern, append(c.h, h...)...)
return c
}
// Post deletegate Post method
func (c *Combo) Post(h ...interface{}) *Combo {
c.r.Post(c.pattern, append(c.h, h...)...)
return c
}
// Delete deletegate Delete method
func (c *Combo) Delete(h ...interface{}) *Combo {
c.r.Delete(c.pattern, append(c.h, h...)...)
return c
}
// Put deletegate Put method
func (c *Combo) Put(h ...interface{}) *Combo {
c.r.Put(c.pattern, append(c.h, h...)...)
return c
}
// Patch deletegate Patch method
func (c *Combo) Patch(h ...interface{}) *Combo {
c.r.Patch(c.pattern, append(c.h, h...)...)
return c
}

169
modules/web/route_test.go Normal file
View file

@ -0,0 +1,169 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package web
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
)
func TestRoute1(t *testing.T) {
buff := bytes.NewBufferString("")
recorder := httptest.NewRecorder()
recorder.Body = buff
r := NewRoute()
r.Get("/{username}/{reponame}/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
username := chi.URLParam(req, "username")
assert.EqualValues(t, "gitea", username)
reponame := chi.URLParam(req, "reponame")
assert.EqualValues(t, "gitea", reponame)
tp := chi.URLParam(req, "type")
assert.EqualValues(t, "issues", tp)
})
req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
}
func TestRoute2(t *testing.T) {
buff := bytes.NewBufferString("")
recorder := httptest.NewRecorder()
recorder.Body = buff
var route int
r := NewRoute()
r.Group("/{username}/{reponame}", func() {
r.Group("", func() {
r.Get("/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
username := chi.URLParam(req, "username")
assert.EqualValues(t, "gitea", username)
reponame := chi.URLParam(req, "reponame")
assert.EqualValues(t, "gitea", reponame)
tp := chi.URLParam(req, "type")
assert.EqualValues(t, "issues", tp)
route = 0
})
r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
username := chi.URLParam(req, "username")
assert.EqualValues(t, "gitea", username)
reponame := chi.URLParam(req, "reponame")
assert.EqualValues(t, "gitea", reponame)
tp := chi.URLParam(req, "type")
assert.EqualValues(t, "issues", tp)
index := chi.URLParam(req, "index")
assert.EqualValues(t, "1", index)
route = 1
})
}, func(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(200)
})
r.Group("/issues/{index}", func() {
r.Get("/view", func(resp http.ResponseWriter, req *http.Request) {
username := chi.URLParam(req, "username")
assert.EqualValues(t, "gitea", username)
reponame := chi.URLParam(req, "reponame")
assert.EqualValues(t, "gitea", reponame)
index := chi.URLParam(req, "index")
assert.EqualValues(t, "1", index)
route = 2
})
})
})
req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 0, route)
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 1, route)
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 2, route)
}
func TestRoute3(t *testing.T) {
buff := bytes.NewBufferString("")
recorder := httptest.NewRecorder()
recorder.Body = buff
var route int
m := NewRoute()
r := NewRoute()
r.Mount("/api/v1", m)
m.Group("/repos", func() {
m.Group("/{username}/{reponame}", func() {
m.Group("/branch_protections", func() {
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
route = 0
})
m.Post("", func(resp http.ResponseWriter, req *http.Request) {
route = 1
})
m.Group("/{name}", func() {
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
route = 2
})
m.Patch("", func(resp http.ResponseWriter, req *http.Request) {
route = 3
})
m.Delete("", func(resp http.ResponseWriter, req *http.Request) {
route = 4
})
})
})
})
})
req, err := http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 0, route)
req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK)
assert.EqualValues(t, 1, route)
req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 2, route)
req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 3, route)
req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
assert.NoError(t, err)
r.ServeHTTP(recorder, req)
assert.EqualValues(t, http.StatusOK, recorder.Code)
assert.EqualValues(t, 4, route)
}

View file

@ -1538,8 +1538,7 @@ settings.trust_model.collaborator.long=Mitarbeiter: Vertraue Signaturen von Mita
settings.trust_model.collaborator.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert - ( egal ob sie mit dem Committer übereinstimmen oder nicht). Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, unabhängig ob die Signatur mit dem Committer übereinstimmt oder nicht. settings.trust_model.collaborator.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert - ( egal ob sie mit dem Committer übereinstimmen oder nicht). Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, unabhängig ob die Signatur mit dem Committer übereinstimmt oder nicht.
settings.trust_model.committer=Committer settings.trust_model.committer=Committer
settings.trust_model.committer.long=Committer: Vertraue Signaturen, die zu Committern passen (Dies stimmt mit GitHub überein und zwingt signierte Commits von Gitea dazu, Gitea als Committer zu haben) settings.trust_model.committer.long=Committer: Vertraue Signaturen, die zu Committern passen (Dies stimmt mit GitHub überein und zwingt signierte Commits von Gitea dazu, Gitea als Committer zu haben)
settings.trust_model.committer.desc=Gültige Signaturen von Mitwirkenden werden als "vertrauenswürdig" gekennzeichnet, wenn sie mit ihrem Committer übereinstimmen. Ansonsten werden sie als "nicht übereinstimmend" markiert. Das führt dazu, dass Gitea auf signierten Commits, bei denen der echte Committer als Co-authored-by: oder Co-committed-by in der Beschreibung eingetragen wurde, als Committer gilt. settings.trust_model.committer.desc=Gültige Signaturen von Mitwirkenden werden als "vertrauenswürdig" gekennzeichnet, wenn sie mit ihrem Committer übereinstimmen. Ansonsten werden sie als "nicht übereinstimmend" markiert. Das führt dazu, dass Gitea auf signierten Commits, bei denen der echte Committer als Co-authored-by: oder Co-committed-by in der Beschreibung eingetragen wurde, als Committer gilt. Der Standard Gitea Schlüssel muss auf einen User in der Datenbank zeigen.
Der Standard Gitea Schlüssel muss auf einen User in der Datenbank zeigen.
settings.trust_model.collaboratorcommitter=Mitarbeiter+Committer settings.trust_model.collaboratorcommitter=Mitarbeiter+Committer
settings.trust_model.collaboratorcommitter.long=Mitarbeiter+Committer: Signaturen der Mitarbeiter vertrauen die mit dem Committer übereinstimmen settings.trust_model.collaboratorcommitter.long=Mitarbeiter+Committer: Signaturen der Mitarbeiter vertrauen die mit dem Committer übereinstimmen
settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, wenn die Signatur mit dem Committer übereinstimmt als "nicht übereinstimmend". Dies zwingt Gitea als Committer bei signierten Commits mit dem tatsächlichen Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen. settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, wenn die Signatur mit dem Committer übereinstimmt als "nicht übereinstimmend". Dies zwingt Gitea als Committer bei signierten Commits mit dem tatsächlichen Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen.

View file

@ -16,20 +16,20 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/cron" "code.gitea.io/gitea/modules/cron"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/mailer"
"gitea.com/macaron/macaron" "gitea.com/go-chi/session"
"gitea.com/macaron/session"
) )
const ( const (
@ -132,7 +132,8 @@ func Dashboard(ctx *context.Context) {
} }
// DashboardPost run an admin operation // DashboardPost run an admin operation
func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) { func DashboardPost(ctx *context.Context) {
form := web.GetForm(ctx).(*auth.AdminDashboardForm)
ctx.Data["Title"] = ctx.Tr("admin.dashboard") ctx.Data["Title"] = ctx.Tr("admin.dashboard")
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminDashboard"] = true ctx.Data["PageIsAdminDashboard"] = true
@ -239,7 +240,7 @@ func Config(ctx *context.Context) {
ctx.Data["OfflineMode"] = setting.OfflineMode ctx.Data["OfflineMode"] = setting.OfflineMode
ctx.Data["DisableRouterLog"] = setting.DisableRouterLog ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
ctx.Data["RunUser"] = setting.RunUser ctx.Data["RunUser"] = setting.RunUser
ctx.Data["RunMode"] = strings.Title(macaron.Env) ctx.Data["RunMode"] = strings.Title(setting.RunMode)
if version, err := git.LocalVersion(); err == nil { if version, err := git.LocalVersion(); err == nil {
ctx.Data["GitVersion"] = version.Original() ctx.Data["GitVersion"] = version.Original()
} }

View file

@ -10,15 +10,16 @@ import (
"regexp" "regexp"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"xorm.io/xorm/convert" "xorm.io/xorm/convert"
) )
@ -206,7 +207,8 @@ func parseSSPIConfig(ctx *context.Context, form auth.AuthenticationForm) (*model
} }
// NewAuthSourcePost response for adding an auth source // NewAuthSourcePost response for adding an auth source
func NewAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) { func NewAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*auth.AuthenticationForm)
ctx.Data["Title"] = ctx.Tr("admin.auths.new") ctx.Data["Title"] = ctx.Tr("admin.auths.new")
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true ctx.Data["PageIsAdminAuthentications"] = true
@ -312,7 +314,8 @@ func EditAuthSource(ctx *context.Context) {
} }
// EditAuthSourcePost response for editing auth source // EditAuthSourcePost response for editing auth source
func EditAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) { func EditAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*auth.AuthenticationForm)
ctx.Data["Title"] = ctx.Tr("admin.auths.edit") ctx.Data["Title"] = ctx.Tr("admin.auths.edit")
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true ctx.Data["PageIsAdminAuthentications"] = true

View file

@ -11,12 +11,13 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password" "code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
router_user_setting "code.gitea.io/gitea/routers/user/setting" router_user_setting "code.gitea.io/gitea/routers/user/setting"
"code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/mailer"
@ -63,7 +64,8 @@ func NewUser(ctx *context.Context) {
} }
// NewUserPost response for adding a new user // NewUserPost response for adding a new user
func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) { func NewUserPost(ctx *context.Context) {
form := web.GetForm(ctx).(*auth.AdminCreateUserForm)
ctx.Data["Title"] = ctx.Tr("admin.users.new_account") ctx.Data["Title"] = ctx.Tr("admin.users.new_account")
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true ctx.Data["PageIsAdminUsers"] = true
@ -214,7 +216,8 @@ func EditUser(ctx *context.Context) {
} }
// EditUserPost response for editting user // EditUserPost response for editting user
func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) { func EditUserPost(ctx *context.Context) {
form := web.GetForm(ctx).(*auth.AdminEditUserForm)
ctx.Data["Title"] = ctx.Tr("admin.users.edit_account") ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true ctx.Data["PageIsAdminUsers"] = true

View file

@ -8,8 +8,9 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -39,7 +40,8 @@ func TestNewUserPost_MustChangePassword(t *testing.T) {
MustChangePassword: true, MustChangePassword: true,
} }
NewUserPost(ctx, form) web.SetForm(ctx, &form)
NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.SuccessMsg) assert.NotEmpty(t, ctx.Flash.SuccessMsg)
@ -76,7 +78,8 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
MustChangePassword: false, MustChangePassword: false,
} }
NewUserPost(ctx, form) web.SetForm(ctx, &form)
NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.SuccessMsg) assert.NotEmpty(t, ctx.Flash.SuccessMsg)
@ -113,7 +116,8 @@ func TestNewUserPost_InvalidEmail(t *testing.T) {
MustChangePassword: false, MustChangePassword: false,
} }
NewUserPost(ctx, form) web.SetForm(ctx, &form)
NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.ErrorMsg) assert.NotEmpty(t, ctx.Flash.ErrorMsg)
} }

View file

@ -13,12 +13,13 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
// CreateOrg api for create organization // CreateOrg api for create organization
func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) { func CreateOrg(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/orgs admin adminCreateOrg // swagger:operation POST /admin/users/{username}/orgs admin adminCreateOrg
// --- // ---
// summary: Create an organization // summary: Create an organization
@ -43,7 +44,7 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateOrgOption)
u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return

View file

@ -7,12 +7,13 @@ package admin
import ( import (
"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/routers/api/v1/repo" "code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
) )
// CreateRepo api for creating a repository // CreateRepo api for creating a repository
func CreateRepo(ctx *context.APIContext, form api.CreateRepoOption) { func CreateRepo(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/repos admin adminCreateRepo // swagger:operation POST /admin/users/{username}/repos admin adminCreateRepo
// --- // ---
// summary: Create a repository on behalf of a user // summary: Create a repository on behalf of a user
@ -41,11 +42,11 @@ func CreateRepo(ctx *context.APIContext, form api.CreateRepoOption) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateRepoOption)
owner := user.GetUserByParams(ctx) owner := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
repo.CreateUserRepo(ctx, owner, form) repo.CreateUserRepo(ctx, owner, *form)
} }

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password" "code.gitea.io/gitea/modules/password"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/mailer"
@ -42,7 +43,7 @@ func parseLoginSource(ctx *context.APIContext, u *models.User, sourceID int64, l
} }
// CreateUser create a user // CreateUser create a user
func CreateUser(ctx *context.APIContext, form api.CreateUserOption) { func CreateUser(ctx *context.APIContext) {
// swagger:operation POST /admin/users admin adminCreateUser // swagger:operation POST /admin/users admin adminCreateUser
// --- // ---
// summary: Create a user // summary: Create a user
@ -64,7 +65,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateUserOption)
u := &models.User{ u := &models.User{
Name: form.Username, Name: form.Username,
FullName: form.FullName, FullName: form.FullName,
@ -119,7 +120,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
} }
// EditUser api for modifying a user's information // EditUser api for modifying a user's information
func EditUser(ctx *context.APIContext, form api.EditUserOption) { func EditUser(ctx *context.APIContext) {
// swagger:operation PATCH /admin/users/{username} admin adminEditUser // swagger:operation PATCH /admin/users/{username} admin adminEditUser
// --- // ---
// summary: Edit an existing user // summary: Edit an existing user
@ -144,7 +145,7 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditUserOption)
u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
@ -283,7 +284,7 @@ func DeleteUser(ctx *context.APIContext) {
} }
// CreatePublicKey api for creating a public key to a user // CreatePublicKey api for creating a public key to a user
func CreatePublicKey(ctx *context.APIContext, form api.CreateKeyOption) { func CreatePublicKey(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/keys admin adminCreatePublicKey // swagger:operation POST /admin/users/{username}/keys admin adminCreatePublicKey
// --- // ---
// summary: Add a public key on behalf of a user // summary: Add a public key on behalf of a user
@ -308,12 +309,12 @@ func CreatePublicKey(ctx *context.APIContext, form api.CreateKeyOption) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateKeyOption)
u := user.GetUserByParams(ctx) u := user.GetUserByParams(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
user.CreateUserPublicKey(ctx, form, u.ID) user.CreateUserPublicKey(ctx, *form, u.ID)
} }
// DeleteUserPublicKey api for deleting a user's public key // DeleteUserPublicKey api for deleting a user's public key

View file

@ -66,14 +66,16 @@ package v1
import ( import (
"net/http" "net/http"
"reflect"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/admin" "code.gitea.io/gitea/routers/api/v1/admin"
"code.gitea.io/gitea/routers/api/v1/misc" "code.gitea.io/gitea/routers/api/v1/misc"
"code.gitea.io/gitea/routers/api/v1/notify" "code.gitea.io/gitea/routers/api/v1/notify"
@ -83,11 +85,12 @@ import (
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation _ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"gitea.com/macaron/binding" "gitea.com/go-chi/binding"
"gitea.com/macaron/macaron" "gitea.com/go-chi/session"
"github.com/go-chi/cors"
) )
func sudo() macaron.Handler { func sudo() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
sudo := ctx.Query("sudo") sudo := ctx.Query("sudo")
if len(sudo) == 0 { if len(sudo) == 0 {
@ -117,10 +120,10 @@ func sudo() macaron.Handler {
} }
} }
func repoAssignment() macaron.Handler { func repoAssignment() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
userName := ctx.Params(":username") userName := ctx.Params("username")
repoName := ctx.Params(":reponame") repoName := ctx.Params("reponame")
var ( var (
owner *models.User owner *models.User
@ -184,7 +187,7 @@ func repoAssignment() macaron.Handler {
} }
// Contexter middleware already checks token for user sign in process. // Contexter middleware already checks token for user sign in process.
func reqToken() macaron.Handler { func reqToken() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if true == ctx.Data["IsApiToken"] { if true == ctx.Data["IsApiToken"] {
return return
@ -201,7 +204,7 @@ func reqToken() macaron.Handler {
} }
} }
func reqBasicAuth() macaron.Handler { func reqBasicAuth() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.Context.IsBasicAuth { if !ctx.Context.IsBasicAuth {
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "basic auth required") ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "basic auth required")
@ -212,7 +215,7 @@ func reqBasicAuth() macaron.Handler {
} }
// reqSiteAdmin user should be the site admin // reqSiteAdmin user should be the site admin
func reqSiteAdmin() macaron.Handler { func reqSiteAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() { if !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin") ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
@ -222,7 +225,7 @@ func reqSiteAdmin() macaron.Handler {
} }
// reqOwner user should be the owner of the repo or site admin. // reqOwner user should be the owner of the repo or site admin.
func reqOwner() macaron.Handler { func reqOwner() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo") ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
@ -232,7 +235,7 @@ func reqOwner() macaron.Handler {
} }
// reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin // reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
func reqAdmin() macaron.Handler { func reqAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository") ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
@ -242,7 +245,7 @@ func reqAdmin() macaron.Handler {
} }
// reqRepoWriter user should have a permission to write to a repo, or be a site admin // reqRepoWriter user should have a permission to write to a repo, or be a site admin
func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler { func reqRepoWriter(unitTypes ...models.UnitType) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo") ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
@ -252,7 +255,7 @@ func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
} }
// reqRepoReader user should have specific read permission or be a repo admin or a site admin // reqRepoReader user should have specific read permission or be a repo admin or a site admin
func reqRepoReader(unitType models.UnitType) macaron.Handler { func reqRepoReader(unitType models.UnitType) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin") ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
@ -262,7 +265,7 @@ func reqRepoReader(unitType models.UnitType) macaron.Handler {
} }
// reqAnyRepoReader user should have any permission to read repository or permissions of site admin // reqAnyRepoReader user should have any permission to read repository or permissions of site admin
func reqAnyRepoReader() macaron.Handler { func reqAnyRepoReader() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() { if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin") ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
@ -272,7 +275,7 @@ func reqAnyRepoReader() macaron.Handler {
} }
// reqOrgOwnership user should be an organization owner, or a site admin // reqOrgOwnership user should be an organization owner, or a site admin
func reqOrgOwnership() macaron.Handler { func reqOrgOwnership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() { if ctx.Context.IsUserSiteAdmin() {
return return
@ -304,7 +307,7 @@ func reqOrgOwnership() macaron.Handler {
} }
// reqTeamMembership user should be an team member, or a site admin // reqTeamMembership user should be an team member, or a site admin
func reqTeamMembership() macaron.Handler { func reqTeamMembership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() { if ctx.Context.IsUserSiteAdmin() {
return return
@ -341,7 +344,7 @@ func reqTeamMembership() macaron.Handler {
} }
// reqOrgMembership user should be an organization member, or a site admin // reqOrgMembership user should be an organization member, or a site admin
func reqOrgMembership() macaron.Handler { func reqOrgMembership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() { if ctx.Context.IsUserSiteAdmin() {
return return
@ -371,7 +374,7 @@ func reqOrgMembership() macaron.Handler {
} }
} }
func reqGitHook() macaron.Handler { func reqGitHook() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) { return func(ctx *context.APIContext) {
if !ctx.User.CanEditGitHook() { if !ctx.User.CanEditGitHook() {
ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks") ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
@ -380,7 +383,7 @@ func reqGitHook() macaron.Handler {
} }
} }
func orgAssignment(args ...bool) macaron.Handler { func orgAssignment(args ...bool) func(ctx *context.APIContext) {
var ( var (
assignOrg bool assignOrg bool
assignTeam bool assignTeam bool
@ -500,13 +503,6 @@ func mustEnableIssuesOrPulls(ctx *context.APIContext) {
} }
} }
func mustEnableUserHeatmap(ctx *context.APIContext) {
if !setting.Service.EnableUserHeatmap {
ctx.NotFound()
return
}
}
func mustNotBeArchived(ctx *context.APIContext) { func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived { if ctx.Repo.Repository.IsArchived {
ctx.NotFound() ctx.NotFound()
@ -514,18 +510,59 @@ func mustNotBeArchived(ctx *context.APIContext) {
} }
} }
// RegisterRoutes registers all v1 APIs routes to web application. // bind binding an obj to a func(ctx *context.APIContext)
func RegisterRoutes(m *macaron.Macaron) { func bind(obj interface{}) http.HandlerFunc {
bind := binding.Bind var tp = reflect.TypeOf(obj)
for tp.Kind() == reflect.Ptr {
if setting.API.EnableSwagger { tp = tp.Elem()
m.Get("/swagger", misc.Swagger) // Render V1 by default
} }
return web.Wrap(func(ctx *context.APIContext) {
var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
errs := binding.Bind(ctx.Req, theObj)
if len(errs) > 0 {
ctx.Error(422, "validationError", errs[0].Error())
return
}
web.SetForm(ctx, theObj)
})
}
m.Group("/v1", func() { // Routes registers all v1 APIs routes to web application.
func Routes() *web.Route {
var m = web.NewRoute()
m.Use(session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
CookiePath: setting.SessionConfig.CookiePath,
Gclifetime: setting.SessionConfig.Gclifetime,
Maxlifetime: setting.SessionConfig.Maxlifetime,
Secure: setting.SessionConfig.Secure,
Domain: setting.SessionConfig.Domain,
}))
m.Use(securityHeaders())
if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{
//Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
AllowedOrigins: setting.CORSConfig.AllowDomain,
//setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials,
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
}))
}
m.Use(context.APIContexter())
m.Use(context.ToggleAPI(&context.ToggleOptions{
SignInRequired: setting.Service.RequireSignInView,
}))
m.Group("", func() {
// Miscellaneous // Miscellaneous
if setting.API.EnableSwagger { if setting.API.EnableSwagger {
m.Get("/swagger", misc.Swagger) m.Get("/swagger", func(ctx *context.APIContext) {
ctx.Redirect("/api/swagger")
})
} }
m.Get("/version", misc.Version) m.Get("/version", misc.Version)
m.Get("/signing-key.gpg", misc.SigningKey) m.Get("/signing-key.gpg", misc.SigningKey)
@ -544,7 +581,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Get(notify.ListNotifications). Get(notify.ListNotifications).
Put(notify.ReadNotifications) Put(notify.ReadNotifications)
m.Get("/new", notify.NewAvailable) m.Get("/new", notify.NewAvailable)
m.Combo("/threads/:id"). m.Combo("/threads/{id}").
Get(notify.GetThread). Get(notify.GetThread).
Patch(notify.ReadThread) Patch(notify.ReadThread)
}, reqToken()) }, reqToken())
@ -553,28 +590,31 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/users", func() { m.Group("/users", func() {
m.Get("/search", user.Search) m.Get("/search", user.Search)
m.Group("/:username", func() { m.Group("/{username}", func() {
m.Get("", user.GetInfo) m.Get("", user.GetInfo)
m.Get("/heatmap", mustEnableUserHeatmap, user.GetUserHeatmapData)
if setting.Service.EnableUserHeatmap {
m.Get("/heatmap", user.GetUserHeatmapData)
}
m.Get("/repos", user.ListUserRepos) m.Get("/repos", user.ListUserRepos)
m.Group("/tokens", func() { m.Group("/tokens", func() {
m.Combo("").Get(user.ListAccessTokens). m.Combo("").Get(user.ListAccessTokens).
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
m.Combo("/:id").Delete(user.DeleteAccessToken) m.Combo("/{id}").Delete(user.DeleteAccessToken)
}, reqBasicAuth()) }, reqBasicAuth())
}) })
}) })
m.Group("/users", func() { m.Group("/users", func() {
m.Group("/:username", func() { m.Group("/{username}", func() {
m.Get("/keys", user.ListPublicKeys) m.Get("/keys", user.ListPublicKeys)
m.Get("/gpg_keys", user.ListGPGKeys) m.Get("/gpg_keys", user.ListGPGKeys)
m.Get("/followers", user.ListFollowers) m.Get("/followers", user.ListFollowers)
m.Group("/following", func() { m.Group("/following", func() {
m.Get("", user.ListFollowing) m.Get("", user.ListFollowing)
m.Get("/:target", user.CheckFollowing) m.Get("/{target}", user.CheckFollowing)
}) })
m.Get("/starred", user.GetStarredRepos) m.Get("/starred", user.GetStarredRepos)
@ -592,20 +632,20 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/followers", user.ListMyFollowers) m.Get("/followers", user.ListMyFollowers)
m.Group("/following", func() { m.Group("/following", func() {
m.Get("", user.ListMyFollowing) m.Get("", user.ListMyFollowing)
m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow) m.Combo("/{username}").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
}) })
m.Group("/keys", func() { m.Group("/keys", func() {
m.Combo("").Get(user.ListMyPublicKeys). m.Combo("").Get(user.ListMyPublicKeys).
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
m.Combo("/:id").Get(user.GetPublicKey). m.Combo("/{id}").Get(user.GetPublicKey).
Delete(user.DeletePublicKey) Delete(user.DeletePublicKey)
}) })
m.Group("/applications", func() { m.Group("/applications", func() {
m.Combo("/oauth2"). m.Combo("/oauth2").
Get(user.ListOauth2Applications). Get(user.ListOauth2Applications).
Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application) Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
m.Combo("/oauth2/:id"). m.Combo("/oauth2/{id}").
Delete(user.DeleteOauth2Application). Delete(user.DeleteOauth2Application).
Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application). Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
Get(user.GetOauth2Application) Get(user.GetOauth2Application)
@ -614,7 +654,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/gpg_keys", func() { m.Group("/gpg_keys", func() {
m.Combo("").Get(user.ListMyGPGKeys). m.Combo("").Get(user.ListMyGPGKeys).
Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey) Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
m.Combo("/:id").Get(user.GetGPGKey). m.Combo("/{id}").Get(user.GetGPGKey).
Delete(user.DeleteGPGKey) Delete(user.DeleteGPGKey)
}) })
@ -623,7 +663,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/starred", func() { m.Group("/starred", func() {
m.Get("", user.GetMyStarredRepos) m.Get("", user.GetMyStarredRepos)
m.Group("/:username/:reponame", func() { m.Group("/{username}/{reponame}", func() {
m.Get("", user.IsStarring) m.Get("", user.IsStarring)
m.Put("", user.Star) m.Put("", user.Star)
m.Delete("", user.Unstar) m.Delete("", user.Unstar)
@ -639,9 +679,9 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqToken()) }, reqToken())
// Repositories // Repositories
m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated) m.Post("/org/{org}/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated)
m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID) m.Combo("/repositories/{id}", reqToken()).Get(repo.GetByID)
m.Group("/repos", func() { m.Group("/repos", func() {
m.Get("/search", repo.Search) m.Get("/search", repo.Search)
@ -650,10 +690,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
m.Group("/:username/:reponame", func() { m.Group("/{username}/{reponame}", func() {
m.Combo("").Get(reqAnyRepoReader(), repo.Get). m.Combo("").Get(reqAnyRepoReader(), repo.Get).
Delete(reqToken(), reqOwner(), repo.Delete). Delete(reqToken(), reqOwner(), repo.Delete).
Patch(reqToken(), reqAdmin(), context.RepoRefForAPI(), bind(api.EditRepoOption{}), repo.Edit) Patch(reqToken(), reqAdmin(), context.RepoRefForAPI, bind(api.EditRepoOption{}), repo.Edit)
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer) m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
m.Combo("/notifications"). m.Combo("/notifications").
Get(reqToken(), notify.ListRepoNotifications). Get(reqToken(), notify.ListRepoNotifications).
@ -661,15 +701,15 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Combo("").Get(repo.ListHooks). m.Combo("").Get(repo.ListHooks).
Post(bind(api.CreateHookOption{}), repo.CreateHook) Post(bind(api.CreateHookOption{}), repo.CreateHook)
m.Group("/:id", func() { m.Group("/{id}", func() {
m.Combo("").Get(repo.GetHook). m.Combo("").Get(repo.GetHook).
Patch(bind(api.EditHookOption{}), repo.EditHook). Patch(bind(api.EditHookOption{}), repo.EditHook).
Delete(repo.DeleteHook) Delete(repo.DeleteHook)
m.Post("/tests", context.RepoRefForAPI(), repo.TestHook) m.Post("/tests", context.RepoRefForAPI, repo.TestHook)
}) })
m.Group("/git", func() { m.Group("/git", func() {
m.Combo("").Get(repo.ListGitHooks) m.Combo("").Get(repo.ListGitHooks)
m.Group("/:id", func() { m.Group("/{id}", func() {
m.Combo("").Get(repo.GetGitHook). m.Combo("").Get(repo.GetGitHook).
Patch(bind(api.EditGitHookOption{}), repo.EditGitHook). Patch(bind(api.EditGitHookOption{}), repo.EditGitHook).
Delete(repo.DeleteGitHook) Delete(repo.DeleteGitHook)
@ -678,11 +718,11 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqToken(), reqAdmin()) }, reqToken(), reqAdmin())
m.Group("/collaborators", func() { m.Group("/collaborators", func() {
m.Get("", reqAnyRepoReader(), repo.ListCollaborators) m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
m.Combo("/:collaborator").Get(reqAnyRepoReader(), repo.IsCollaborator). m.Combo("/{collaborator}").Get(reqAnyRepoReader(), repo.IsCollaborator).
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
Delete(reqAdmin(), repo.DeleteCollaborator) Delete(reqAdmin(), repo.DeleteCollaborator)
}, reqToken()) }, reqToken())
m.Get("/raw/*", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetRawFile) m.Get("/raw/*", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive) m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive)
m.Combo("/forks").Get(repo.ListForks). m.Combo("/forks").Get(repo.ListForks).
Post(reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.CreateForkOption{}), repo.CreateFork) Post(reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
@ -695,7 +735,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/branch_protections", func() { m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections) m.Get("", repo.ListBranchProtections)
m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection) m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
m.Group("/:name", func() { m.Group("/{name}", func() {
m.Get("", repo.GetBranchProtection) m.Get("", repo.GetBranchProtection)
m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection) m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
m.Delete("", repo.DeleteBranchProtection) m.Delete("", repo.DeleteBranchProtection)
@ -707,19 +747,19 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/keys", func() { m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys). m.Combo("").Get(repo.ListDeployKeys).
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
m.Combo("/:id").Get(repo.GetDeployKey). m.Combo("/{id}").Get(repo.GetDeployKey).
Delete(repo.DeleteDeploykey) Delete(repo.DeleteDeploykey)
}, reqToken(), reqAdmin()) }, reqToken(), reqAdmin())
m.Group("/times", func() { m.Group("/times", func() {
m.Combo("").Get(repo.ListTrackedTimesByRepository) m.Combo("").Get(repo.ListTrackedTimesByRepository)
m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser) m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
}, mustEnableIssues, reqToken()) }, mustEnableIssues, reqToken())
m.Group("/issues", func() { m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues). m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue) Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Group("/comments", func() { m.Group("/comments", func() {
m.Get("", repo.ListRepoIssueComments) m.Get("", repo.ListRepoIssueComments)
m.Group("/:id", func() { m.Group("/{id}", func() {
m.Combo(""). m.Combo("").
Get(repo.GetIssueComment). Get(repo.GetIssueComment).
Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment). Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
@ -730,13 +770,13 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction) Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
}) })
}) })
m.Group("/:index", func() { m.Group("/{index}", func() {
m.Combo("").Get(repo.GetIssue). m.Combo("").Get(repo.GetIssue).
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue) Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
m.Group("/comments", func() { m.Group("/comments", func() {
m.Combo("").Get(repo.ListIssueComments). m.Combo("").Get(repo.ListIssueComments).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated). m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
Delete(repo.DeleteIssueCommentDeprecated) Delete(repo.DeleteIssueCommentDeprecated)
}) })
m.Group("/labels", func() { m.Group("/labels", func() {
@ -744,14 +784,14 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels). Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels). Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
Delete(reqToken(), repo.ClearIssueLabels) Delete(reqToken(), repo.ClearIssueLabels)
m.Delete("/:id", reqToken(), repo.DeleteIssueLabel) m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel)
}) })
m.Group("/times", func() { m.Group("/times", func() {
m.Combo(""). m.Combo("").
Get(repo.ListTrackedTimes). Get(repo.ListTrackedTimes).
Post(bind(api.AddTimeOption{}), repo.AddTime). Post(bind(api.AddTimeOption{}), repo.AddTime).
Delete(repo.ResetIssueTime) Delete(repo.ResetIssueTime)
m.Delete("/:id", repo.DeleteTime) m.Delete("/{id}", repo.DeleteTime)
}, reqToken()) }, reqToken())
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline) m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
m.Group("/stopwatch", func() { m.Group("/stopwatch", func() {
@ -762,8 +802,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/subscriptions", func() { m.Group("/subscriptions", func() {
m.Get("", repo.GetIssueSubscribers) m.Get("", repo.GetIssueSubscribers)
m.Get("/check", reqToken(), repo.CheckIssueSubscription) m.Get("/check", reqToken(), repo.CheckIssueSubscription)
m.Put("/:user", reqToken(), repo.AddIssueSubscription) m.Put("/{user}", reqToken(), repo.AddIssueSubscription)
m.Delete("/:user", reqToken(), repo.DelIssueSubscription) m.Delete("/{user}", reqToken(), repo.DelIssueSubscription)
}) })
m.Combo("/reactions"). m.Combo("/reactions").
Get(repo.GetIssueReactions). Get(repo.GetIssueReactions).
@ -774,7 +814,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/labels", func() { m.Group("/labels", func() {
m.Combo("").Get(repo.ListLabels). m.Combo("").Get(repo.ListLabels).
Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel) Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel)
m.Combo("/:id").Get(repo.GetLabel). m.Combo("/{id}").Get(repo.GetLabel).
Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel). Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteLabel) Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteLabel)
}) })
@ -783,7 +823,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/milestones", func() { m.Group("/milestones", func() {
m.Combo("").Get(repo.ListMilestones). m.Combo("").Get(repo.ListMilestones).
Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
m.Combo("/:id").Get(repo.GetMilestone). m.Combo("/{id}").Get(repo.GetMilestone).
Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone) Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone)
}) })
@ -797,30 +837,30 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/releases", func() { m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases). m.Combo("").Get(repo.ListReleases).
Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.CreateReleaseOption{}), repo.CreateRelease) Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.CreateReleaseOption{}), repo.CreateRelease)
m.Group("/:id", func() { m.Group("/{id}", func() {
m.Combo("").Get(repo.GetRelease). m.Combo("").Get(repo.GetRelease).
Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.EditReleaseOption{}), repo.EditRelease). Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.EditReleaseOption{}), repo.EditRelease).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteRelease) Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteRelease)
m.Group("/assets", func() { m.Group("/assets", func() {
m.Combo("").Get(repo.ListReleaseAttachments). m.Combo("").Get(repo.ListReleaseAttachments).
Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.CreateReleaseAttachment) Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.CreateReleaseAttachment)
m.Combo("/:asset").Get(repo.GetReleaseAttachment). m.Combo("/{asset}").Get(repo.GetReleaseAttachment).
Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment) Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment)
}) })
}) })
m.Group("/tags", func() { m.Group("/tags", func() {
m.Combo("/:tag"). m.Combo("/{tag}").
Get(repo.GetReleaseTag). Get(repo.GetReleaseTag).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag) Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
}) })
}, reqRepoReader(models.UnitTypeReleases)) }, reqRepoReader(models.UnitTypeReleases))
m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync) m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)
m.Get("/editorconfig/:filename", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig) m.Get("/editorconfig/{filename}", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig)
m.Group("/pulls", func() { m.Group("/pulls", func() {
m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests). m.Combo("").Get(repo.ListPullRequests).
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
m.Group("/:index", func() { m.Group("/{index}", func() {
m.Combo("").Get(repo.GetPullRequest). m.Combo("").Get(repo.GetPullRequest).
Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest) Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
m.Get(".diff", repo.DownloadPullDiff) m.Get(".diff", repo.DownloadPullDiff)
@ -832,7 +872,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo(""). m.Combo("").
Get(repo.ListPullReviews). Get(repo.ListPullReviews).
Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview) Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
m.Group("/:id", func() { m.Group("/{id}", func() {
m.Combo(""). m.Combo("").
Get(repo.GetPullReview). Get(repo.GetPullReview).
Delete(reqToken(), repo.DeletePullReview). Delete(reqToken(), repo.DeletePullReview).
@ -847,25 +887,25 @@ func RegisterRoutes(m *macaron.Macaron) {
}) })
}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false)) }, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
m.Group("/statuses", func() { m.Group("/statuses", func() {
m.Combo("/:sha").Get(repo.GetCommitStatuses). m.Combo("/{sha}").Get(repo.GetCommitStatuses).
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
}, reqRepoReader(models.UnitTypeCode)) }, reqRepoReader(models.UnitTypeCode))
m.Group("/commits", func() { m.Group("/commits", func() {
m.Get("", repo.GetAllCommits) m.Get("", repo.GetAllCommits)
m.Group("/:ref", func() { m.Group("/{ref}", func() {
m.Get("/status", repo.GetCombinedCommitStatusByRef) m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef) m.Get("/statuses", repo.GetCommitStatusesByRef)
}) })
}, reqRepoReader(models.UnitTypeCode)) }, reqRepoReader(models.UnitTypeCode))
m.Group("/git", func() { m.Group("/git", func() {
m.Group("/commits", func() { m.Group("/commits", func() {
m.Get("/:sha", repo.GetSingleCommit) m.Get("/{sha}", repo.GetSingleCommit)
}) })
m.Get("/refs", repo.GetGitAllRefs) m.Get("/refs", repo.GetGitAllRefs)
m.Get("/refs/*", repo.GetGitRefs) m.Get("/refs/*", repo.GetGitRefs)
m.Get("/trees/:sha", context.RepoRefForAPI(), repo.GetTree) m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree)
m.Get("/blobs/:sha", context.RepoRefForAPI(), repo.GetBlob) m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob)
m.Get("/tags/:sha", context.RepoRefForAPI(), repo.GetTag) m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetTag)
}, reqRepoReader(models.UnitTypeCode)) }, reqRepoReader(models.UnitTypeCode))
m.Group("/contents", func() { m.Group("/contents", func() {
m.Get("", repo.GetContentsList) m.Get("", repo.GetContentsList)
@ -880,7 +920,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/topics", func() { m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics). m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics) Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
m.Group("/:topic", func() { m.Group("/{topic}", func() {
m.Combo("").Put(reqToken(), repo.AddTopic). m.Combo("").Put(reqToken(), repo.AddTopic).
Delete(reqToken(), repo.DeleteTopic) Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin()) }, reqAdmin())
@ -892,10 +932,10 @@ func RegisterRoutes(m *macaron.Macaron) {
// Organizations // Organizations
m.Get("/user/orgs", reqToken(), org.ListMyOrgs) m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
m.Get("/users/:username/orgs", org.ListUserOrgs) m.Get("/users/{username}/orgs", org.ListUserOrgs)
m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create) m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll) m.Get("/orgs", org.GetAll)
m.Group("/orgs/:org", func() { m.Group("/orgs/{org}", func() {
m.Combo("").Get(org.Get). m.Combo("").Get(org.Get).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit). Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
Delete(reqToken(), reqOrgOwnership(), org.Delete) Delete(reqToken(), reqOrgOwnership(), org.Delete)
@ -903,12 +943,12 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
m.Group("/members", func() { m.Group("/members", func() {
m.Get("", org.ListMembers) m.Get("", org.ListMembers)
m.Combo("/:username").Get(org.IsMember). m.Combo("/{username}").Get(org.IsMember).
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember) Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
}) })
m.Group("/public_members", func() { m.Group("/public_members", func() {
m.Get("", org.ListPublicMembers) m.Get("", org.ListPublicMembers)
m.Combo("/:username").Get(org.IsPublicMember). m.Combo("/{username}").Get(org.IsPublicMember).
Put(reqToken(), reqOrgMembership(), org.PublicizeMember). Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
Delete(reqToken(), reqOrgMembership(), org.ConcealMember) Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
}) })
@ -920,56 +960,52 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/labels", func() { m.Group("/labels", func() {
m.Get("", org.ListLabels) m.Get("", org.ListLabels)
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel) m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
m.Combo("/:id").Get(org.GetLabel). m.Combo("/{id}").Get(org.GetLabel).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel). Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel) Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
}) })
m.Group("/hooks", func() { m.Group("/hooks", func() {
m.Combo("").Get(org.ListHooks). m.Combo("").Get(org.ListHooks).
Post(bind(api.CreateHookOption{}), org.CreateHook) Post(bind(api.CreateHookOption{}), org.CreateHook)
m.Combo("/:id").Get(org.GetHook). m.Combo("/{id}").Get(org.GetHook).
Patch(bind(api.EditHookOption{}), org.EditHook). Patch(bind(api.EditHookOption{}), org.EditHook).
Delete(org.DeleteHook) Delete(org.DeleteHook)
}, reqToken(), reqOrgOwnership()) }, reqToken(), reqOrgOwnership())
}, orgAssignment(true)) }, orgAssignment(true))
m.Group("/teams/:teamid", func() { m.Group("/teams/{teamid}", func() {
m.Combo("").Get(org.GetTeam). m.Combo("").Get(org.GetTeam).
Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam). Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
Delete(reqOrgOwnership(), org.DeleteTeam) Delete(reqOrgOwnership(), org.DeleteTeam)
m.Group("/members", func() { m.Group("/members", func() {
m.Get("", org.GetTeamMembers) m.Get("", org.GetTeamMembers)
m.Combo("/:username"). m.Combo("/{username}").
Get(org.GetTeamMember). Get(org.GetTeamMember).
Put(reqOrgOwnership(), org.AddTeamMember). Put(reqOrgOwnership(), org.AddTeamMember).
Delete(reqOrgOwnership(), org.RemoveTeamMember) Delete(reqOrgOwnership(), org.RemoveTeamMember)
}) })
m.Group("/repos", func() { m.Group("/repos", func() {
m.Get("", org.GetTeamRepos) m.Get("", org.GetTeamRepos)
m.Combo("/:org/:reponame"). m.Combo("/{org}/{reponame}").
Put(org.AddTeamRepository). Put(org.AddTeamRepository).
Delete(org.RemoveTeamRepository) Delete(org.RemoveTeamRepository)
}) })
}, orgAssignment(false, true), reqToken(), reqTeamMembership()) }, orgAssignment(false, true), reqToken(), reqTeamMembership())
m.Any("/*", func(ctx *context.APIContext) {
ctx.NotFound()
})
m.Group("/admin", func() { m.Group("/admin", func() {
m.Group("/cron", func() { m.Group("/cron", func() {
m.Get("", admin.ListCronTasks) m.Get("", admin.ListCronTasks)
m.Post("/:task", admin.PostCronTask) m.Post("/{task}", admin.PostCronTask)
}) })
m.Get("/orgs", admin.GetAllOrgs) m.Get("/orgs", admin.GetAllOrgs)
m.Group("/users", func() { m.Group("/users", func() {
m.Get("", admin.GetAllUsers) m.Get("", admin.GetAllUsers)
m.Post("", bind(api.CreateUserOption{}), admin.CreateUser) m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
m.Group("/:username", func() { m.Group("/{username}", func() {
m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser). m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
Delete(admin.DeleteUser) Delete(admin.DeleteUser)
m.Group("/keys", func() { m.Group("/keys", func() {
m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey) m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
m.Delete("/:id", admin.DeleteUserPublicKey) m.Delete("/{id}", admin.DeleteUserPublicKey)
}) })
m.Get("/orgs", org.ListUserOrgs) m.Get("/orgs", org.ListUserOrgs)
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
@ -978,23 +1014,26 @@ func RegisterRoutes(m *macaron.Macaron) {
}) })
m.Group("/unadopted", func() { m.Group("/unadopted", func() {
m.Get("", admin.ListUnadoptedRepositories) m.Get("", admin.ListUnadoptedRepositories)
m.Post("/:username/:reponame", admin.AdoptRepository) m.Post("/{username}/{reponame}", admin.AdoptRepository)
m.Delete("/:username/:reponame", admin.DeleteUnadoptedRepository) m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
}) })
}, reqToken(), reqSiteAdmin()) }, reqToken(), reqSiteAdmin())
m.Group("/topics", func() { m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch) m.Get("/search", repo.TopicSearch)
}) })
}, securityHeaders(), context.APIContexter(), sudo()) }, sudo())
return m
} }
func securityHeaders() macaron.Handler { func securityHeaders() func(http.Handler) http.Handler {
return func(ctx *macaron.Context) { return func(next http.Handler) http.Handler {
ctx.Resp.Before(func(w macaron.ResponseWriter) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers // CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
// http://stackoverflow.com/a/3146618/244009 // http://stackoverflow.com/a/3146618/244009
w.Header().Set("x-content-type-options", "nosniff") resp.Header().Set("x-content-type-options", "nosniff")
next.ServeHTTP(resp, req)
}) })
} }
} }

View file

@ -5,6 +5,7 @@
package misc package misc
import ( import (
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
@ -13,12 +14,13 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"mvdan.cc/xurls/v2" "mvdan.cc/xurls/v2"
) )
// Markdown render markdown document to HTML // Markdown render markdown document to HTML
func Markdown(ctx *context.APIContext, form api.MarkdownOption) { func Markdown(ctx *context.APIContext) {
// swagger:operation POST /markdown miscellaneous renderMarkdown // swagger:operation POST /markdown miscellaneous renderMarkdown
// --- // ---
// summary: Render a markdown document as HTML // summary: Render a markdown document as HTML
@ -37,6 +39,8 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.MarkdownOption)
if ctx.HasAPIError() { if ctx.HasAPIError() {
ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg()) ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
return return
@ -117,7 +121,7 @@ func MarkdownRaw(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
body, err := ctx.Req.Body().Bytes() body, err := ioutil.ReadAll(ctx.Req.Body)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
return return

View file

@ -15,10 +15,10 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"gitea.com/macaron/inject"
"gitea.com/macaron/macaron"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -26,25 +26,21 @@ const AppURL = "http://localhost:3000/"
const Repo = "gogits/gogs" const Repo = "gogits/gogs"
const AppSubURL = AppURL + Repo + "/" const AppSubURL = AppURL + Repo + "/"
func createContext(req *http.Request) (*macaron.Context, *httptest.ResponseRecorder) { func createContext(req *http.Request) (*context.Context, *httptest.ResponseRecorder) {
var rnd = templates.HTMLRenderer()
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
c := &macaron.Context{ c := &context.Context{
Injector: inject.New(), Req: req,
Req: macaron.Request{Request: req}, Resp: context.NewResponse(resp),
Resp: macaron.NewResponseWriter(req.Method, resp), Render: rnd,
Render: &macaron.DummyRender{ResponseWriter: resp}, Data: make(map[string]interface{}),
Data: make(map[string]interface{}),
} }
c.Map(c)
c.Map(req)
return c, resp return c, resp
} }
func wrap(ctx *macaron.Context) *context.APIContext { func wrap(ctx *context.Context) *context.APIContext {
return &context.APIContext{ return &context.APIContext{
Context: &context.Context{ Context: ctx,
Context: ctx,
},
} }
} }
@ -115,7 +111,8 @@ Here are some links to the most important topics. You can find the full list of
for i := 0; i < len(testCases); i += 2 { for i := 0; i < len(testCases); i += 2 {
options.Text = testCases[i] options.Text = testCases[i]
Markdown(ctx, options) web.SetForm(ctx, &options)
Markdown(ctx)
assert.Equal(t, testCases[i+1], resp.Body.String()) assert.Equal(t, testCases[i+1], resp.Body.String())
resp.Body.Reset() resp.Body.Reset()
} }
@ -156,7 +153,8 @@ func TestAPI_RenderSimple(t *testing.T) {
for i := 0; i < len(simpleCases); i += 2 { for i := 0; i < len(simpleCases); i += 2 {
options.Text = simpleCases[i] options.Text = simpleCases[i]
Markdown(ctx, options) web.SetForm(ctx, &options)
Markdown(ctx)
assert.Equal(t, simpleCases[i+1], resp.Body.String()) assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset() resp.Body.Reset()
} }
@ -174,7 +172,7 @@ func TestAPI_RenderRaw(t *testing.T) {
ctx := wrap(m) ctx := wrap(m)
for i := 0; i < len(simpleCases); i += 2 { for i := 0; i < len(simpleCases); i += 2 {
ctx.Req.Request.Body = ioutil.NopCloser(strings.NewReader(simpleCases[i])) ctx.Req.Body = ioutil.NopCloser(strings.NewReader(simpleCases[i]))
MarkdownRaw(ctx) MarkdownRaw(ctx)
assert.Equal(t, simpleCases[i+1], resp.Body.String()) assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset() resp.Body.Reset()

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -85,7 +86,7 @@ func GetHook(ctx *context.APIContext) {
} }
// CreateHook create a hook for an organization // CreateHook create a hook for an organization
func CreateHook(ctx *context.APIContext, form api.CreateHookOption) { func CreateHook(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/hooks/ organization orgCreateHook // swagger:operation POST /orgs/{org}/hooks/ organization orgCreateHook
// --- // ---
// summary: Create a hook // summary: Create a hook
@ -108,15 +109,16 @@ func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
// "201": // "201":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.CreateHookOption)
//TODO in body params //TODO in body params
if !utils.CheckCreateHookOption(ctx, &form) { if !utils.CheckCreateHookOption(ctx, form) {
return return
} }
utils.AddOrgHook(ctx, &form) utils.AddOrgHook(ctx, form)
} }
// EditHook modify a hook of a repository // EditHook modify a hook of a repository
func EditHook(ctx *context.APIContext, form api.EditHookOption) { func EditHook(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook // swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook
// --- // ---
// summary: Update a hook // summary: Update a hook
@ -144,9 +146,11 @@ func EditHook(ctx *context.APIContext, form api.EditHookOption) {
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.EditHookOption)
//TODO in body params //TODO in body params
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
utils.EditOrgHook(ctx, &form, hookID) utils.EditOrgHook(ctx, form, hookID)
} }
// DeleteHook delete a hook of an organization // DeleteHook delete a hook of an organization

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -52,7 +53,7 @@ func ListLabels(ctx *context.APIContext) {
} }
// CreateLabel create a label for a repository // CreateLabel create a label for a repository
func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) { func CreateLabel(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/labels organization orgCreateLabel // swagger:operation POST /orgs/{org}/labels organization orgCreateLabel
// --- // ---
// summary: Create a label for an organization // summary: Create a label for an organization
@ -75,7 +76,7 @@ func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) {
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ") form.Color = strings.Trim(form.Color, " ")
if len(form.Color) == 6 { if len(form.Color) == 6 {
form.Color = "#" + form.Color form.Color = "#" + form.Color
@ -144,7 +145,7 @@ func GetLabel(ctx *context.APIContext) {
} }
// EditLabel modify a label for an Organization // EditLabel modify a label for an Organization
func EditLabel(ctx *context.APIContext, form api.EditLabelOption) { func EditLabel(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/labels/{id} organization orgEditLabel // swagger:operation PATCH /orgs/{org}/labels/{id} organization orgEditLabel
// --- // ---
// summary: Update a label // summary: Update a label
@ -173,7 +174,7 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
// "$ref": "#/responses/Label" // "$ref": "#/responses/Label"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption)
label, err := models.GetLabelInOrgByID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) label, err := models.GetLabelInOrgByID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrOrgLabelNotExist(err) { if models.IsErrOrgLabelNotExist(err) {

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -149,7 +150,7 @@ func GetAll(ctx *context.APIContext) {
} }
// Create api for create organization // Create api for create organization
func Create(ctx *context.APIContext, form api.CreateOrgOption) { func Create(ctx *context.APIContext) {
// swagger:operation POST /orgs organization orgCreate // swagger:operation POST /orgs organization orgCreate
// --- // ---
// summary: Create an organization // summary: Create an organization
@ -169,7 +170,7 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateOrgOption)
if !ctx.User.CanCreateOrganization() { if !ctx.User.CanCreateOrganization() {
ctx.Error(http.StatusForbidden, "Create organization not allowed", nil) ctx.Error(http.StatusForbidden, "Create organization not allowed", nil)
return return
@ -231,7 +232,7 @@ func Get(ctx *context.APIContext) {
} }
// Edit change an organization's information // Edit change an organization's information
func Edit(ctx *context.APIContext, form api.EditOrgOption) { func Edit(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org} organization orgEdit // swagger:operation PATCH /orgs/{org} organization orgEdit
// --- // ---
// summary: Edit an organization // summary: Edit an organization
@ -253,7 +254,7 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Organization" // "$ref": "#/responses/Organization"
form := web.GetForm(ctx).(*api.EditOrgOption)
org := ctx.Org.Organization org := ctx.Org.Organization
org.FullName = form.FullName org.FullName = form.FullName
org.Description = form.Description org.Description = form.Description

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -131,7 +132,7 @@ func GetTeam(ctx *context.APIContext) {
} }
// CreateTeam api for create a team // CreateTeam api for create a team
func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) { func CreateTeam(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/teams organization orgCreateTeam // swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
// --- // ---
// summary: Create a team // summary: Create a team
@ -154,7 +155,7 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
// "$ref": "#/responses/Team" // "$ref": "#/responses/Team"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateTeamOption)
team := &models.Team{ team := &models.Team{
OrgID: ctx.Org.Organization.ID, OrgID: ctx.Org.Organization.ID,
Name: form.Name, Name: form.Name,
@ -190,7 +191,7 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
} }
// EditTeam api for edit a team // EditTeam api for edit a team
func EditTeam(ctx *context.APIContext, form api.EditTeamOption) { func EditTeam(ctx *context.APIContext) {
// swagger:operation PATCH /teams/{id} organization orgEditTeam // swagger:operation PATCH /teams/{id} organization orgEditTeam
// --- // ---
// summary: Edit a team // summary: Edit a team
@ -212,6 +213,8 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
// "200": // "200":
// "$ref": "#/responses/Team" // "$ref": "#/responses/Team"
form := web.GetForm(ctx).(*api.EditTeamOption)
team := ctx.Org.Team team := ctx.Org.Team
if err := team.GetUnits(); err != nil { if err := team.GetUnits(); err != nil {
ctx.InternalServerError(err) ctx.InternalServerError(err)

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
) )
@ -175,7 +176,7 @@ func DeleteBranch(ctx *context.APIContext) {
} }
// CreateBranch creates a branch for a user's repository // CreateBranch creates a branch for a user's repository
func CreateBranch(ctx *context.APIContext, opt api.CreateBranchRepoOption) { func CreateBranch(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
// --- // ---
// summary: Create a branch // summary: Create a branch
@ -206,6 +207,7 @@ func CreateBranch(ctx *context.APIContext, opt api.CreateBranchRepoOption) {
// "409": // "409":
// description: The branch with the same name already exists. // description: The branch with the same name already exists.
opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
return return
@ -395,7 +397,7 @@ func ListBranchProtections(ctx *context.APIContext) {
} }
// CreateBranchProtection creates a branch protection for a repo // CreateBranchProtection creates a branch protection for a repo
func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtectionOption) { func CreateBranchProtection(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
// --- // ---
// summary: Create a branch protections for a repository // summary: Create a branch protections for a repository
@ -428,6 +430,7 @@ func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtec
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
// Currently protection must match an actual branch // Currently protection must match an actual branch
@ -561,7 +564,7 @@ func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtec
} }
// EditBranchProtection edits a branch protection for a repo // EditBranchProtection edits a branch protection for a repo
func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtectionOption) { func EditBranchProtection(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
// --- // ---
// summary: Edit a branch protections for a repository. Only fields that are set will be changed // summary: Edit a branch protections for a repository. Only fields that are set will be changed
@ -596,7 +599,7 @@ func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtection
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
bpName := ctx.Params(":name") bpName := ctx.Params(":name")
protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName) protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -111,7 +112,7 @@ func IsCollaborator(ctx *context.APIContext) {
} }
// AddCollaborator add a collaborator to a repository // AddCollaborator add a collaborator to a repository
func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) { func AddCollaborator(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/collaborators/{collaborator} repository repoAddCollaborator // swagger:operation PUT /repos/{owner}/{repo}/collaborators/{collaborator} repository repoAddCollaborator
// --- // ---
// summary: Add a collaborator to a repository // summary: Add a collaborator to a repository
@ -143,6 +144,8 @@ func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.AddCollaboratorOption)
collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) collaborator, err := models.GetUserByName(ctx.Params(":collaborator"))
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {

View file

@ -69,7 +69,11 @@ func getCommit(ctx *context.APIContext, identifier string) {
defer gitRepo.Close() defer gitRepo.Close()
commit, err := gitRepo.GetCommit(identifier) commit, err := gitRepo.GetCommit(identifier)
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err) if git.IsErrNotExist(err) {
ctx.NotFound(identifier)
return
}
ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
return return
} }

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/repo" "code.gitea.io/gitea/routers/repo"
) )
@ -167,7 +168,7 @@ func canReadFiles(r *context.Repository) bool {
} }
// CreateFile handles API call for creating a file // CreateFile handles API call for creating a file
func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) { func CreateFile(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile // swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
// --- // ---
// summary: Create a file in a repository // summary: Create a file in a repository
@ -206,6 +207,7 @@ func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
// "422": // "422":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty")) ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
} }
@ -253,7 +255,7 @@ func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
} }
// UpdateFile handles API call for updating a file // UpdateFile handles API call for updating a file
func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) { func UpdateFile(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile // swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
// --- // ---
// summary: Update a file in a repository // summary: Update a file in a repository
@ -291,7 +293,7 @@ func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
// "422": // "422":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
if ctx.Repo.Repository.IsEmpty { if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty")) ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
} }
@ -377,7 +379,7 @@ func createOrUpdateFile(ctx *context.APIContext, opts *repofiles.UpdateRepoFileO
} }
// DeleteFile Delete a fle in a repository // DeleteFile Delete a fle in a repository
func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) { func DeleteFile(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile // swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
// --- // ---
// summary: Delete a file in a repository // summary: Delete a file in a repository
@ -416,6 +418,7 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
// "404": // "404":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
if !canWriteFiles(ctx.Repo) { if !canWriteFiles(ctx.Repo) {
ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{ ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID, UserID: ctx.User.ID,

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
) )
@ -65,7 +66,7 @@ func ListForks(ctx *context.APIContext) {
} }
// CreateFork create a fork of a repo // CreateFork create a fork of a repo
func CreateFork(ctx *context.APIContext, form api.CreateForkOption) { func CreateFork(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/forks repository createFork // swagger:operation POST /repos/{owner}/{repo}/forks repository createFork
// --- // ---
// summary: Fork a repository // summary: Fork a repository
@ -94,6 +95,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateForkOption)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
var forker *models.User // user/org that will own the fork var forker *models.User // user/org that will own the fork
if form.Organization == nil { if form.Organization == nil {

View file

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
) )
// ListGitHooks list all Git hooks of a repository // ListGitHooks list all Git hooks of a repository
@ -91,7 +92,7 @@ func GetGitHook(ctx *context.APIContext) {
} }
// EditGitHook modify a Git hook of a repository // EditGitHook modify a Git hook of a repository
func EditGitHook(ctx *context.APIContext, form api.EditGitHookOption) { func EditGitHook(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/hooks/git/{id} repository repoEditGitHook // swagger:operation PATCH /repos/{owner}/{repo}/hooks/git/{id} repository repoEditGitHook
// --- // ---
// summary: Edit a Git hook in a repository // summary: Edit a Git hook in a repository
@ -123,6 +124,7 @@ func EditGitHook(ctx *context.APIContext, form api.EditGitHookOption) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditGitHookOption)
hookID := ctx.Params(":id") hookID := ctx.Params(":id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/webhook" "code.gitea.io/gitea/services/webhook"
) )
@ -158,7 +159,7 @@ func TestHook(ctx *context.APIContext) {
} }
// CreateHook create a hook for a repository // CreateHook create a hook for a repository
func CreateHook(ctx *context.APIContext, form api.CreateHookOption) { func CreateHook(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/hooks repository repoCreateHook // swagger:operation POST /repos/{owner}/{repo}/hooks repository repoCreateHook
// --- // ---
// summary: Create a hook // summary: Create a hook
@ -184,14 +185,16 @@ func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
// responses: // responses:
// "201": // "201":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
if !utils.CheckCreateHookOption(ctx, &form) { form := web.GetForm(ctx).(*api.CreateHookOption)
if !utils.CheckCreateHookOption(ctx, form) {
return return
} }
utils.AddRepoHook(ctx, &form) utils.AddRepoHook(ctx, form)
} }
// EditHook modify a hook of a repository // EditHook modify a hook of a repository
func EditHook(ctx *context.APIContext, form api.EditHookOption) { func EditHook(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/hooks/{id} repository repoEditHook // swagger:operation PATCH /repos/{owner}/{repo}/hooks/{id} repository repoEditHook
// --- // ---
// summary: Edit a hook in a repository // summary: Edit a hook in a repository
@ -221,8 +224,9 @@ func EditHook(ctx *context.APIContext, form api.EditHookOption) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.EditHookOption)
hookID := ctx.ParamsInt64(":id") hookID := ctx.ParamsInt64(":id")
utils.EditRepoHook(ctx, &form, hookID) utils.EditRepoHook(ctx, form, hookID)
} }
// DeleteHook delete a hook of a repository // DeleteHook delete a hook of a repository

View file

@ -22,6 +22,7 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
) )
@ -448,7 +449,7 @@ func GetIssue(ctx *context.APIContext) {
} }
// CreateIssue create an issue of a repository // CreateIssue create an issue of a repository
func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { func CreateIssue(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues issue issueCreateIssue // swagger:operation POST /repos/{owner}/{repo}/issues issue issueCreateIssue
// --- // ---
// summary: Create an issue. If using deadline only the date will be taken into account, and time of day ignored. // summary: Create an issue. If using deadline only the date will be taken into account, and time of day ignored.
@ -480,7 +481,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateIssueOption)
var deadlineUnix timeutil.TimeStamp var deadlineUnix timeutil.TimeStamp
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) { if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix()) deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
@ -564,7 +565,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
} }
// EditIssue modify an issue of a repository // EditIssue modify an issue of a repository
func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { func EditIssue(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue // swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
// --- // ---
// summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored. // summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored.
@ -603,6 +604,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
// "412": // "412":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
form := web.GetForm(ctx).(*api.EditIssueOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {
@ -723,7 +725,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
} }
// UpdateIssueDeadline updates an issue deadline // UpdateIssueDeadline updates an issue deadline
func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) { func UpdateIssueDeadline(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline
// --- // ---
// summary: Set an issue deadline. If set to null, the deadline is deleted. If using deadline only the date will be taken into account, and time of day ignored. // summary: Set an issue deadline. If set to null, the deadline is deleted. If using deadline only the date will be taken into account, and time of day ignored.
@ -759,7 +761,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditDeadlineOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
comment_service "code.gitea.io/gitea/services/comments" comment_service "code.gitea.io/gitea/services/comments"
) )
@ -174,7 +175,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
} }
// CreateIssueComment create a comment for an issue // CreateIssueComment create a comment for an issue
func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOption) { func CreateIssueComment(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/comments issue issueCreateComment // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/comments issue issueCreateComment
// --- // ---
// summary: Add a comment to an issue // summary: Add a comment to an issue
@ -208,7 +209,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti
// "$ref": "#/responses/Comment" // "$ref": "#/responses/Comment"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err) ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
@ -298,7 +299,7 @@ func GetIssueComment(ctx *context.APIContext) {
} }
// EditIssueComment modify a comment of an issue // EditIssueComment modify a comment of an issue
func EditIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) { func EditIssueComment(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id} issue issueEditComment // swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id} issue issueEditComment
// --- // ---
// summary: Edit a comment // summary: Edit a comment
@ -337,11 +338,12 @@ func EditIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
editIssueComment(ctx, form) form := web.GetForm(ctx).(*api.EditIssueCommentOption)
editIssueComment(ctx, *form)
} }
// EditIssueCommentDeprecated modify a comment of an issue // EditIssueCommentDeprecated modify a comment of an issue
func EditIssueCommentDeprecated(ctx *context.APIContext, form api.EditIssueCommentOption) { func EditIssueCommentDeprecated(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueEditCommentDeprecated // swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueEditCommentDeprecated
// --- // ---
// summary: Edit a comment // summary: Edit a comment
@ -386,7 +388,8 @@ func EditIssueCommentDeprecated(ctx *context.APIContext, form api.EditIssueComme
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
editIssueComment(ctx, form) form := web.GetForm(ctx).(*api.EditIssueCommentOption)
editIssueComment(ctx, *form)
} }
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) { func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
) )
@ -64,7 +65,7 @@ func ListIssueLabels(ctx *context.APIContext) {
} }
// AddIssueLabels add labels for an issue // AddIssueLabels add labels for an issue
func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { func AddIssueLabels(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/labels issue issueAddLabel // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/labels issue issueAddLabel
// --- // ---
// summary: Add a label to an issue // summary: Add a label to an issue
@ -99,7 +100,8 @@ func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
issue, labels, err := prepareForReplaceOrAdd(ctx, form) form := web.GetForm(ctx).(*api.IssueLabelsOption)
issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
if err != nil { if err != nil {
return return
} }
@ -190,7 +192,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
} }
// ReplaceIssueLabels replace labels for an issue // ReplaceIssueLabels replace labels for an issue
func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { func ReplaceIssueLabels(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/labels issue issueReplaceLabels // swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/labels issue issueReplaceLabels
// --- // ---
// summary: Replace an issue's labels // summary: Replace an issue's labels
@ -224,8 +226,8 @@ func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// "$ref": "#/responses/LabelList" // "$ref": "#/responses/LabelList"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.IssueLabelsOption)
issue, labels, err := prepareForReplaceOrAdd(ctx, form) issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
if err != nil { if err != nil {
return return
} }

View file

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -90,7 +91,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
} }
// PostIssueCommentReaction add a reaction to a comment of an issue // PostIssueCommentReaction add a reaction to a comment of an issue
func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) { func PostIssueCommentReaction(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction // swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
// --- // ---
// summary: Add a reaction to a comment of an issue // summary: Add a reaction to a comment of an issue
@ -127,11 +128,13 @@ func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOpti
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
changeIssueCommentReaction(ctx, form, true) form := web.GetForm(ctx).(*api.EditReactionOption)
changeIssueCommentReaction(ctx, *form, true)
} }
// DeleteIssueCommentReaction remove a reaction from a comment of an issue // DeleteIssueCommentReaction remove a reaction from a comment of an issue
func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) { func DeleteIssueCommentReaction(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
// --- // ---
// summary: Remove a reaction from a comment of an issue // summary: Remove a reaction from a comment of an issue
@ -166,7 +169,9 @@ func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
changeIssueCommentReaction(ctx, form, false) form := web.GetForm(ctx).(*api.EditReactionOption)
changeIssueCommentReaction(ctx, *form, false)
} }
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) { func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
@ -304,7 +309,7 @@ func GetIssueReactions(ctx *context.APIContext) {
} }
// PostIssueReaction add a reaction to an issue // PostIssueReaction add a reaction to an issue
func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) { func PostIssueReaction(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
// --- // ---
// summary: Add a reaction to an issue // summary: Add a reaction to an issue
@ -340,12 +345,12 @@ func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
// "$ref": "#/responses/Reaction" // "$ref": "#/responses/Reaction"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.EditReactionOption)
changeIssueReaction(ctx, form, true) changeIssueReaction(ctx, *form, true)
} }
// DeleteIssueReaction remove a reaction from an issue // DeleteIssueReaction remove a reaction from an issue
func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) { func DeleteIssueReaction(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
// --- // ---
// summary: Remove a reaction from an issue // summary: Remove a reaction from an issue
@ -379,8 +384,8 @@ func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.EditReactionOption)
changeIssueReaction(ctx, form, false) changeIssueReaction(ctx, *form, false)
} }
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) { func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -132,7 +133,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
} }
// AddTime add time manual to the given issue // AddTime add time manual to the given issue
func AddTime(ctx *context.APIContext, form api.AddTimeOption) { func AddTime(ctx *context.APIContext) {
// swagger:operation Post /repos/{owner}/{repo}/issues/{index}/times issue issueAddTime // swagger:operation Post /repos/{owner}/{repo}/issues/{index}/times issue issueAddTime
// --- // ---
// summary: Add tracked time to a issue // summary: Add tracked time to a issue
@ -168,7 +169,7 @@ func AddTime(ctx *context.APIContext, form api.AddTimeOption) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.AddTimeOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil { if err != nil {
if models.IsErrIssueNotExist(err) { if models.IsErrIssueNotExist(err) {

Some files were not shown because too many files have changed in this diff Show more