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>
tokarchuk/v1.17
Lunny Xiao 4 years ago committed by GitHub
parent 3adbbb4255
commit 6433ba0ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .golangci.yml
  2. 2
      cmd/dump.go
  3. 9
      cmd/web.go
  4. 4
      contrib/pr/checkout.go
  5. 6
      docs/content/doc/advanced/config-cheat-sheet.en-us.md
  6. 37
      docs/content/doc/advanced/logging-documentation.en-us.md
  7. 2
      docs/content/page/index.en-us.md
  8. 2
      docs/content/page/index.fr-fr.md
  9. 2
      docs/content/page/index.zh-cn.md
  10. 2
      docs/content/page/index.zh-tw.md
  11. 16
      go.mod
  12. 60
      go.sum
  13. 2
      integrations/api_helper_for_declarative_test.go
  14. 2
      integrations/api_pull_test.go
  15. 8
      integrations/api_releases_test.go
  16. 10
      integrations/create_no_session_test.go
  17. 11
      integrations/integration_test.go
  18. 8
      integrations/lfs_getobject_test.go
  19. 3
      integrations/links_test.go
  20. 12
      modules/auth/sso/interface.go
  21. 2
      modules/auth/sso/oauth2.go
  22. 6
      modules/cache/cache.go
  23. 2
      modules/cache/cache_redis.go
  24. 141
      modules/context/api.go
  25. 129
      modules/context/auth.go
  26. 26
      modules/context/captcha.go
  27. 559
      modules/context/context.go
  28. 144
      modules/context/csrf.go
  29. 227
      modules/context/form.go
  30. 4
      modules/context/org.go
  31. 12
      modules/context/permission.go
  32. 45
      modules/context/private.go
  33. 570
      modules/context/repo.go
  34. 29
      modules/context/response.go
  35. 100
      modules/context/secret.go
  36. 13
      modules/context/xsrf.go
  37. 90
      modules/context/xsrf_test.go
  38. 25
      modules/forms/admin.go
  39. 15
      modules/forms/auth_form.go
  40. 24
      modules/forms/org.go
  41. 15
      modules/forms/repo_branch_form.go
  42. 173
      modules/forms/repo_form.go
  43. 2
      modules/forms/repo_form_test.go
  44. 108
      modules/forms/user_form.go
  45. 24
      modules/forms/user_form_auth_openid.go
  46. 2
      modules/forms/user_form_test.go
  47. 4
      modules/lfs/locks.go
  48. 13
      modules/lfs/server.go
  49. 16
      modules/middlewares/binding.go
  50. 61
      modules/middlewares/cookie.go
  51. 10
      modules/middlewares/data.go
  52. 65
      modules/middlewares/flash.go
  53. 8
      modules/middlewares/locale.go
  54. 217
      modules/middlewares/redis.go
  55. 196
      modules/middlewares/virtual.go
  56. 2
      modules/session/redis.go
  57. 12
      modules/session/store.go
  58. 13
      modules/session/virtual.go
  59. 12
      modules/setting/log.go
  60. 2
      modules/setting/session.go
  61. 14
      modules/templates/base.go
  62. 25
      modules/templates/dynamic.go
  63. 112
      modules/templates/static.go
  64. 109
      modules/test/context_tests.go
  65. 12
      modules/timeutil/since_test.go
  66. 49
      modules/translation/translation.go
  67. 2
      modules/validation/binding.go
  68. 11
      modules/validation/binding_test.go
  69. 2
      modules/validation/glob_pattern_test.go
  70. 2
      modules/validation/refname_test.go
  71. 2
      modules/validation/validurl_test.go
  72. 322
      modules/web/route.go
  73. 169
      modules/web/route_test.go
  74. 3
      options/locale/locale_de-DE.ini
  75. 11
      routers/admin/admin.go
  76. 9
      routers/admin/auths.go
  77. 9
      routers/admin/users.go
  78. 12
      routers/admin/users_test.go
  79. 5
      routers/api/v1/admin/org.go
  80. 7
      routers/api/v1/admin/repo.go
  81. 15
      routers/api/v1/admin/user.go
  82. 245
      routers/api/v1/api.go
  83. 8
      routers/api/v1/misc/markdown.go
  84. 34
      routers/api/v1/misc/markdown_test.go
  85. 14
      routers/api/v1/org/hook.go
  86. 9
      routers/api/v1/org/label.go
  87. 9
      routers/api/v1/org/org.go
  88. 9
      routers/api/v1/org/team.go
  89. 11
      routers/api/v1/repo/branch.go
  90. 5
      routers/api/v1/repo/collaborators.go
  91. 6
      routers/api/v1/repo/commits.go
  92. 11
      routers/api/v1/repo/file.go
  93. 4
      routers/api/v1/repo/fork.go
  94. 4
      routers/api/v1/repo/git_hook.go
  95. 14
      routers/api/v1/repo/hook.go
  96. 12
      routers/api/v1/repo/issue.go
  97. 15
      routers/api/v1/repo/issue_comment.go
  98. 12
      routers/api/v1/repo/issue_label.go
  99. 25
      routers/api/v1/repo/issue_reaction.go
  100. 5
      routers/api/v1/repo/issue_tracked_time.go
  101. Some files were not shown because too many files have changed in this diff Show More

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

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

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

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

@ -549,7 +549,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
## 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.
- `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.
@ -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`.
- `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\]
- `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`: **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\]`.
@ -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_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:
- `Ctx`: the `macaron.Context` of the request.
- `Ctx`: the `context.Context` of the request.
- `Identity`: the SignedUserName or `"-"` if not logged in.
- `Start`: the start time of the request.
- `ResponseWriter`: the responseWriter from the request.

@ -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
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
There are two types of Router log. By default Macaron send its own
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`.
You can disable 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
configuration. `ROUTER` will default to `console` if unset. The Gitea
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:
- `Ctx` is the `macaron.Context`
- `Ctx` is the `context.Context`
- `Identity` is the `SignedUserName` or `"-"` if the user is not logged
in
- `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
the standard panic recovery trap. The template should also be as simple

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

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

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

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

@ -5,19 +5,12 @@ go 1.14
require (
code.gitea.io/gitea-vet v0.2.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/lunny/levelqueue v0.3.0
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
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/NYTimes/gziphandler v1.1.1
github.com/PuerkitoBio/goquery v1.5.1
github.com/RoaringBitmap/roaring v0.5.5 // indirect
github.com/alecthomas/chroma v0.8.2
@ -36,6 +29,7 @@ require (
github.com/gliderlabs/ssh v0.3.1
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
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-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.2.0

@ -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/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
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/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0=
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/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/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
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.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/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/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
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/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/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/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=
@ -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/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/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/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/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
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/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/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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-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/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/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
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/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-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/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
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/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.9.2/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.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/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 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/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
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 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/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/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
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-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-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/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
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/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
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.46.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.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

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

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

@ -131,7 +131,7 @@ func TestAPIGetReleaseByTag(t *testing.T) {
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)
req := NewRequestf(t, "GET", urlStr)
@ -144,7 +144,7 @@ func TestAPIGetReleaseByTag(t *testing.T) {
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)
req = NewRequestf(t, "GET", urlStr)
@ -163,7 +163,7 @@ func TestAPIDeleteTagByName(t *testing.T) {
session := loginUser(t, owner.LowerName)
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)
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
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)
req = NewRequestf(t, http.MethodDelete, urlStr)

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

@ -31,15 +31,15 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
"github.com/PuerkitoBio/goquery"
"github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
)
var c chi.Router
var c *web.Route
type NilResponseRecorder struct {
httptest.ResponseRecorder
@ -66,9 +66,7 @@ func TestMain(m *testing.M) {
defer cancel()
initIntegrationTest()
c = routes.NewChi()
c.Mount("/", routes.NormalRoutes())
routes.DelegateToMacaron(c)
c = routes.NormalRoutes()
// integration test settings...
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 {
t.Helper()
if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") {
urlStr = "/" + urlStr
}
request, err := http.NewRequest(method, urlStr, body)
assert.NoError(t, err)
request.RequestURI = urlStr

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

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

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

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

@ -10,9 +10,9 @@ import (
"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 (
@ -20,7 +20,7 @@ var (
)
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
return mc.NewCacher(cacheConfig.Adapter, mc.Options{
return mc.NewCacher(mc.Options{
Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval,

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

@ -6,18 +6,21 @@
package context
import (
"context"
"fmt"
"html"
"net/http"
"net/url"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/csrf"
"gitea.com/macaron/macaron"
"gitea.com/go-chi/session"
)
// 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 {
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 = ""
}
}
@ -108,7 +111,7 @@ func (ctx *APIContext) InternalServerError(err error) {
log.ErrorWithSkip(1, "InternalServerError: %v", err)
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()
}
@ -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 {
page := NewPagination(total, pageSize, curPage, 0)
paginater := page.Paginater
@ -172,7 +189,7 @@ func (ctx *APIContext) RequireCSRF() {
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
if len(headerToken) > 0 || len(formValueToken) > 0 {
csrf.Validate(ctx.Context.Context, ctx.csrf)
Validate(ctx.Context, ctx.csrf)
} else {
ctx.Context.Error(401, "Missing CSRF token.")
}
@ -201,42 +218,91 @@ func (ctx *APIContext) CheckForOTP() {
}
// APIContexter returns apicontext as macaron middleware
func APIContexter() macaron.Handler {
return func(c *Context) {
ctx := &APIContext{
Context: c,
}
c.Map(ctx)
func APIContexter() func(http.Handler) http.Handler {
var csrfOpts = getCsrfOpts()
return func(next http.Handler) http.Handler {
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
func ReferencesGitRepo(allowEmpty bool) macaron.Handler {
return func(ctx *APIContext) {
// Empty repository does not have reference information.
if !allowEmpty && ctx.Repo.Repository.IsEmpty {
return
}
// 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)
func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
// Empty repository does not have reference information.
if !allowEmpty && ctx.Repo.Repository.IsEmpty {
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()
// 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()
}
}()
}
ctx.Next()
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
func RepoRefForAPI() macaron.Handler {
return func(ctx *APIContext) {
func RepoRefForAPI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
return
@ -319,6 +386,6 @@ func RepoRefForAPI() macaron.Handler {
return
}
ctx.Next()
}
next.ServeHTTP(w, req)
})
}

@ -7,12 +7,8 @@ package context
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/csrf"
"gitea.com/macaron/macaron"
)
// ToggleOptions contains required or check options
@ -24,42 +20,23 @@ type ToggleOptions struct {
}
// Toggle returns toggle options as middleware
func Toggle(options *ToggleOptions) macaron.Handler {
func Toggle(options *ToggleOptions) func(ctx *Context) {
return func(ctx *Context) {
isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path)
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
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")
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())
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")
return
}
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" {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
@ -82,8 +59,8 @@ func Toggle(options *ToggleOptions) macaron.Handler {
return
}
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" && !auth.IsAPIPath(ctx.Req.URL.Path) {
csrf.Validate(ctx.Context, ctx.csrf)
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
Validate(ctx, ctx.csrf)
if ctx.Written() {
return
}
@ -91,13 +68,6 @@ func Toggle(options *ToggleOptions) macaron.Handler {
if options.SignInRequired {
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" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
}
@ -108,19 +78,88 @@ func Toggle(options *ToggleOptions) macaron.Handler {
ctx.HTML(200, "user/auth/activate")
return
}
if ctx.IsSigned && isAPIPath && ctx.IsBasicAuth {
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !ctx.IsSigned &&
len(ctx.GetCookie(setting.CookieUserName)) > 0 {
if ctx.Req.URL.Path != "/user/events" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
}
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
if options.AdminRequired {
if !ctx.User.IsAdmin {
ctx.Error(403)
return
}
ctx.Data["PageIsAdmin"] = true
}
}
}
// 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.Error(500)
ctx.InternalServerError(err)
return
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
ctx.Error(500)
ctx.InternalServerError(err)
return
}
if !ok {
@ -132,19 +171,11 @@ func Toggle(options *ToggleOptions) macaron.Handler {
}
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !ctx.IsSigned && !isAPIPath &&
len(ctx.GetCookie(setting.CookieUserName)) > 0 {
if ctx.Req.URL.Path != "/user/events" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
}
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
if options.AdminRequired {
if !ctx.User.IsAdmin {
ctx.Error(403)
ctx.JSON(403, map[string]string{
"message": "You have no permission to request for this.",
})
return
}
ctx.Data["PageIsAdmin"] = true

@ -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
}

@ -6,37 +6,55 @@
package context
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"html"
"html/template"
"io"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"gitea.com/macaron/cache"
"gitea.com/macaron/csrf"
"gitea.com/macaron/i18n"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
"gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
"github.com/go-chi/chi"
"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.
type Context struct {
*macaron.Context
Resp ResponseWriter
Req *http.Request
Data map[string]interface{}
Render Render
translation.Locale
Cache cache.Cache
csrf csrf.CSRF
Flash *session.Flash
csrf CSRF
Flash *middlewares.Flash
Session session.Store
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.
func (ctx *Context) HTML(status int, name base.TplName) {
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.
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
if form != nil {
auth.AssignForm(form, ctx.Data)
middlewares.AssignForm(form, ctx.Data)
}
ctx.Flash.ErrorMsg = msg
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) {
if err != nil {
log.ErrorWithSkip(2, "%s: %v", title, err)
if macaron.Env != macaron.PROD {
if !setting.IsProd() {
ctx.Data["ErrorMsg"] = err
}
}
@ -203,7 +230,7 @@ func (ctx *Context) ServerError(title string, err error) {
func (ctx *Context) serverErrorInternal(title string, err error) {
if err != nil {
log.ErrorWithSkip(2, "%s: %v", title, err)
if macaron.Env != macaron.PROD {
if !setting.IsProd() {
ctx.Data["ErrorMsg"] = err
}
}
@ -224,6 +251,44 @@ func (ctx *Context) NotFoundOrServerError(title string, errck func(error) bool,
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
func (ctx *Context) HandleText(status int, title string) {
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("Pragma", "public")
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.
func Contexter() macaron.Handler {
return func(c *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
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{},
func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer()
var c cache.Cache
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)
}
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 == "" {
_, _ = c.Write([]byte(`<!doctype html>
}
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>
<body>
invalid import path
</body>
</html>
`))
c.WriteHeader(400)
return
}
branchName := "master"
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err == nil && len(repo.DefaultBranch) > 0 {
branchName = repo.DefaultBranch
}
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
appURL, _ := url.Parse(setting.AppURL)
insecure := ""
if appURL.Scheme == string(setting.HTTP) {
insecure = "--insecure "
}
c.Header().Set("Content-Type", "text/html")
c.WriteHeader(http.StatusOK)
_, _ = c.Write([]byte(com.Expand(`<!doctype html>
ctx.Status(400)
return
}
branchName := "master"
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err == nil && len(repo.DefaultBranch) > 0 {
branchName = repo.DefaultBranch
}
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
appURL, _ := url.Parse(setting.AppURL)
insecure := ""
if appURL.Scheme == string(setting.HTTP) {
insecure = "--insecure "
}
ctx.Header().Set("Content-Type", "text/html")
ctx.Status(http.StatusOK)
_, _ = ctx.Write([]byte(com.Expand(`<!doctype html>
<html>
<head>
<meta name="go-import" content="{GoGetImport} git {CloneLink}">
@ -319,60 +642,72 @@ func Contexter() macaron.Handler {
</body>
</html>
`, map[string]string{
"GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
"CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
"GoDocDirectory": prefix + "{/dir}",
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
"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)
"GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
"CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
"GoDocDirectory": prefix + "{/dir}",
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
"Insecure": insecure,
})))
return
}
}
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken())
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
log.Debug("Session ID: %s", sess.ID())
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
// 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["EnableSwagger"] = setting.API.EnableSwagger
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
// 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.Data["ManifestData"] = setting.ManifestData
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
log.Debug("Session ID: %s", ctx.Session.ID())
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
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
}
}
c.Map(ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}

@ -1,5 +1,6 @@
// Copyright 2013 Martini Authors
// Copyright 2014 The Macaron Authors
// Copyright 2021 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
@ -13,24 +14,17 @@
// License for the specific language governing permissions and limitations
// under the License.
// Package csrf is a middleware that generates and validates CSRF tokens for Macaron.
package csrf
// a middleware that generates and validates CSRF tokens.
package context
import (
"net/http"
"time"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
"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.
type CSRF interface {
// Return HTTP header to search for token.
@ -42,7 +36,7 @@ type CSRF interface {
// Return cookie path
GetCookiePath() string
// Return the flag value used for the csrf token.
GetCookieHttpOnly() bool
GetCookieHTTPOnly() bool
// Return the token.
GetToken() string
// Validate by token.
@ -63,7 +57,7 @@ type csrf struct {
//Cookie path
CookiePath string
// 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 string
// This value must be unique per user.
@ -94,9 +88,9 @@ func (c *csrf) GetCookiePath() string {
return c.CookiePath
}
// GetCookieHttpOnly returns the flag value used for the csrf token.
func (c *csrf) GetCookieHttpOnly() bool {
return c.CookieHttpOnly
// GetCookieHTTPOnly returns the flag value used for the csrf token.
func (c *csrf) GetCookieHTTPOnly() bool {
return c.CookieHTTPOnly
}
// GetToken returns the current token. This is typically used
@ -115,8 +109,8 @@ func (c *csrf) Error(w http.ResponseWriter) {
c.ErrorFunc(w)
}
// Options maintains options to manage behavior of Generate.
type Options struct {
// CsrfOptions maintains options to manage behavior of Generate.
type CsrfOptions struct {
// The global secret value used to generate Tokens.
Secret string
// HTTP header used to set and get token.
@ -129,11 +123,13 @@ type Options struct {
CookieDomain string
// Cookie path.
CookiePath string
CookieHttpOnly bool
CookieHTTPOnly bool
// SameSite set the cookie SameSite type
SameSite http.SameSite
// Key used for getting the unique ID per user.
SessionKey string
// oldSeesionKey saves old value corresponding to SessionKey.
oldSeesionKey string
// oldSessionKey saves old value corresponding to SessionKey.
oldSessionKey string
// If true, send token via X-CSRFToken header.
SetHeader bool
// If true, send token via _csrf cookie.
@ -144,10 +140,12 @@ type Options struct {
Origin bool
// The function called when Validate fails.
ErrorFunc func(w http.ResponseWriter)
// Cookie life time. Default is 0
CookieLifeTime int
}
func prepareOptions(options []Options) Options {
var opt Options
func prepareOptions(options []CsrfOptions) CsrfOptions {
var opt CsrfOptions
if len(options) > 0 {
opt = options[0]
}
@ -171,7 +169,7 @@ func prepareOptions(options []Options) Options {
if len(opt.SessionKey) == 0 {
opt.SessionKey = "uid"
}
opt.oldSeesionKey = "_old_" + opt.SessionKey
opt.oldSessionKey = "_old_" + opt.SessionKey
if opt.ErrorFunc == nil {
opt.ErrorFunc = func(w http.ResponseWriter) {
http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
@ -181,73 +179,73 @@ func prepareOptions(options []Options) Options {
return opt
}
// Generate 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.
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))
func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
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
}
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
return x
}
x.ID = "0"
uid := sess.Get(opt.SessionKey)
if uid != nil {
x.ID = com.ToStr(uid)
}
x.ID = "0"
uid := ctx.Session.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)
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 {
// 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
}
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 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)
}
}
if opt.SetHeader {
ctx.Resp.Header().Add(opt.Header, x.Token)
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
},
)
}
}
}
// 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.
func Csrfer(options ...Options) macaron.Handler {
return Generate(options...)
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"
// 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.
// 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 !x.ValidToken(token) {
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())

@ -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
}

@ -10,8 +10,6 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
"gitea.com/macaron/macaron"
)
// Organization contains organization context
@ -173,7 +171,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
}
// 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) {
HandleOrgAssignment(ctx, args...)
}

@ -7,12 +7,10 @@ package context
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"gitea.com/macaron/macaron"
)
// RequireRepoAdmin returns a macaron middleware for requiring repository admin permission
func RequireRepoAdmin() macaron.Handler {
func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
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
func RequireRepoWriter(unitType models.UnitType) macaron.Handler {
func RequireRepoWriter(unitType models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanWrite(unitType) {
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
func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler {
func RequireRepoWriterOr(unitTypes ...models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
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
func RequireRepoReader(unitType models.UnitType) macaron.Handler {
func RequireRepoReader(unitType models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanRead(unitType) {
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
func RequireRepoReaderOr(unitTypes ...models.UnitType) macaron.Handler {
func RequireRepoReaderOr(unitTypes ...models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) {

@ -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)
})
}
}

@ -8,6 +8,7 @@ package context
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
@ -21,7 +22,6 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
"github.com/editorconfig/editorconfig-core-go/v2"
"github.com/unknwon/com"
)
@ -81,7 +81,7 @@ func (r *Repository) CanCreateBranch() bool {
}
// RepoMustNotBeArchived checks if a repo is archived
func RepoMustNotBeArchived() macaron.Handler {
func RepoMustNotBeArchived() func(ctx *Context) {
return func(ctx *Context) {
if ctx.Repo.Repository.IsArchived {
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.
func RepoIDAssignment() macaron.Handler {
func RepoIDAssignment() func(ctx *Context) {
return func(ctx *Context) {
repoID := ctx.ParamsInt64(":repoid")
@ -394,223 +394,220 @@ func RepoIDAssignment() macaron.Handler {
}
// RepoAssignment returns a macaron to handle repository assignment
func RepoAssignment() macaron.Handler {
return func(ctx *Context) {
var (
owner *models.User
err error
)
userName := ctx.Params(":username")
repoName := ctx.Params(":reponame")
func RepoAssignment() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var (
owner *models.User
err error
ctx = GetContext(req)
)
userName := ctx.Params(":username")
repoName := ctx.Params(":reponame")
repoName = strings.TrimSuffix(repoName, ".git")
// Check if the user is the same as the repository owner
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
owner = ctx.User
} else {
owner, err = models.GetUserByName(userName)
if err != nil {
if models.IsErrUserNotExist(err) {
if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
}
ctx.NotFound("GetUserByName", nil)
} else {
ctx.ServerError("GetUserByName", err)
}
return
}
}
ctx.Repo.Owner = owner
ctx.Data["Username"] = ctx.Repo.Owner.Name
// Check if the user is the same as the repository owner
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
owner = ctx.User
} else {
owner, err = models.GetUserByName(userName)
// Get repository.
repo, err := models.GetRepositoryByName(owner.ID, repoName)
if err != nil {
if models.IsErrUserNotExist(err) {
redirectUserID, err := models.LookupUserRedirect(userName)
if models.IsErrRepoNotExist(err) {
redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
if err == nil {
RedirectToUser(ctx, userName, redirectUserID)
} else if models.IsErrUserRedirectNotExist(err) {
RedirectToRepo(ctx, redirectRepoID)
} else if models.IsErrRepoRedirectNotExist(err) {
if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
}
ctx.NotFound("GetUserByName", nil)
ctx.NotFound("GetRepositoryByName", nil)
} else {
ctx.ServerError("LookupUserRedirect", err)
ctx.ServerError("LookupRepoRedirect", err)
}
} else {
ctx.ServerError("GetUserByName", err)
ctx.ServerError("GetRepositoryByName", err)
}
return
}
}
ctx.Repo.Owner = owner
ctx.Data["Username"] = ctx.Repo.Owner.Name
repo.Owner = owner
// 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 {
ctx.ServerError("GetRepositoryByName", err)
repoAssignment(ctx, repo)
if ctx.Written() {
return
}
return
}
repo.Owner = owner
repoAssignment(ctx, repo)
if ctx.Written() {
return
}
ctx.Repo.RepoLink = repo.Link()
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
ctx.Repo.RepoLink = repo.Link()
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
if err == nil {
ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
}
unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
if err == nil {
ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
}
ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
IncludeTags: true,
})
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{
IncludeTags: true,
})
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["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()
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 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.IsFork {
RetrieveBaseRepo(ctx, repo)
if ctx.Written() {
return
if repo.IsGenerated() {
RetrieveTemplateRepo(ctx, repo)
if ctx.Written() {
return
}
}
}
if repo.IsGenerated() {
RetrieveTemplateRepo(ctx, repo)
if ctx.Written() {
// Disable everything when the repo is being created
if ctx.Repo.Repository.IsBeingCreated() {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
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()
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
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
ctx.Next()
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()
}
}()
tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil {
ctx.ServerError("GetTags", err)
return
}
ctx.Data["Tags"] = tags
// 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
}
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]
tags, err := ctx.Repo.GitRepo.GetTags()
if err != nil {
ctx.ServerError("GetTags", err)
return
}
}
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
ctx.Data["Tags"] = tags
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()
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)
})
}
}
@ -636,7 +633,7 @@ const (
// RepoRef handles repository reference names when the ref name is not
// 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
return RepoRefByType(RepoRefBranch)
}
@ -715,132 +712,135 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
// RepoRefByType handles repository reference name for a specific type
// of repository reference
func RepoRefByType(refType RepoRefType) macaron.Handler {
return func(ctx *Context) {
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
return
}
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)
func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetContext(req)
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
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.
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()
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("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)
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return
}
refName = brs[0]
}
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
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()
}
}()
}
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)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
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)
} else {
refName = getRefName(ctx, refType)
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
}
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)
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 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))
ctx.Data["BranchName"] = ctx.Repo.BranchName
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.Data["BranchName"] = ctx.Repo.BranchName
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()
next.ServeHTTP(w, req)
})
}
}
// GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler {
func GitHookService() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.User.CanEditGitHook() {
ctx.NotFound("GitHookService", nil)
@ -850,7 +850,7 @@ func GitHookService() macaron.Handler {
}
// 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) {
ctx.Data["UnitTypeCode"] = models.UnitTypeCode
ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues

@ -11,6 +11,7 @@ type ResponseWriter interface {
http.ResponseWriter
Flush()
Status() int
Before(func(ResponseWriter))
}
var (
@ -20,11 +21,19 @@ var (
// Response represents a response
type Response struct {
http.ResponseWriter
status int
status int
befores []func(ResponseWriter)
beforeExecuted bool
}
// Write writes bytes to HTTP endpoint
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)
if err != nil {
return 0, err
@ -37,6 +46,12 @@ func (r *Response) Write(bs []byte) (int, error) {
// WriteHeader write status code
func (r *Response) WriteHeader(statusCode int) {
if !r.beforeExecuted {
for _, before := range r.befores {
before(r)
}
r.beforeExecuted = true
}
r.status = statusCode
r.ResponseWriter.WriteHeader(statusCode)
}
@ -53,10 +68,20 @@ func (r *Response) Status() int {
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
func NewResponse(resp http.ResponseWriter) *Response {
if v, ok := resp.(*Response); ok {
return v
}
return &Response{resp, 0}
return &Response{
ResponseWriter: resp,
status: 0,
befores: make([]func(ResponseWriter), 0),
}
}

@ -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
}

@ -1,5 +1,6 @@
// 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.
@ -13,7 +14,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package csrf
package context
import (
"bytes"
@ -27,13 +28,13 @@ import (
"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.
const TIMEOUT = 24 * time.Hour
const Timeout = 24 * time.Hour
// clean sanitizes a string for inclusion in a token by replacing all ":"s.
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.
@ -53,7 +54,7 @@ func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
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 {
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)
// Check that the token is not expired.
if now.Sub(issueTime) >= TIMEOUT {
if now.Sub(issueTime) >= Timeout {
return false
}

@ -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))
}
})
}

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

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

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

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

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

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

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

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

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

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

@ -22,7 +22,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"gitea.com/macaron/macaron"
"github.com/dgrijalva/jwt-go"
)
@ -413,8 +412,8 @@ func PutHandler(ctx *context.Context) {
}
contentStore := &ContentStore{ObjectStorage: storage.LFS}
defer ctx.Req.Request.Body.Close()
if err := contentStore.Put(meta, ctx.Req.Request.Body); err != nil {
defer ctx.Req.Body.Close()
if err := contentStore.Put(meta, ctx.Req.Body); err != nil {
// Put will log the error itself
ctx.Resp.WriteHeader(500)
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
// 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"), ";")
mt := mediaParts[0]
return mt == metaMediaType
@ -530,7 +529,7 @@ func unpack(ctx *context.Context) *RequestVars {
if r.Method == "POST" { // Maybe also check if +json
var p RequestVars
bodyReader := r.Body().ReadCloser()
bodyReader := r.Body
defer bodyReader.Close()
dec := json.NewDecoder(bodyReader)
err := dec.Decode(&p)
@ -553,7 +552,7 @@ func unpackbatch(ctx *context.Context) *BatchVars {
r := ctx.Req
var bv BatchVars
bodyReader := r.Body().ReadCloser()
bodyReader := r.Body
defer bodyReader.Close()
dec := json.NewDecoder(bodyReader)
err := dec.Decode(&bv)
@ -586,7 +585,7 @@ func writeStatus(ctx *context.Context, status int) {
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)
}

@ -3,24 +3,19 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
package middlewares
import (
"reflect"
"strings"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/validation"
"gitea.com/macaron/binding"
"gitea.com/macaron/macaron"
"gitea.com/go-chi/binding"
"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
type Form interface {
binding.Validator
@ -35,7 +30,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
typ := reflect.TypeOf(form)
val := reflect.ValueOf(form)
if typ.Kind() == reflect.Ptr {
for typ.Kind() == reflect.Ptr {
typ = typ.Elem()
val = val.Elem()
}
@ -84,7 +79,8 @@ func GetInclude(field reflect.StructField) string {
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 {
return errs
}

@ -1,3 +1,4 @@
// Copyright 2020 The Macaron Authors
// 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.
@ -12,6 +13,56 @@ import (
"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
func NewCookie(name, value string, maxAge int) *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())
}
// 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
}

@ -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{}
}

@ -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...)
}

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

@ -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{})
}

@ -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
}

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

@ -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
}

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

@ -254,17 +254,6 @@ func generateNamedLogger(key string, options defaultLogOptions) *LogDescription
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() {
EnableAccessLog = Cfg.Section("log").Key("ENABLE_ACCESS_LOG").MustBool(false)
AccessLogTemplate = Cfg.Section("log").Key("ACCESS_LOG_TEMPLATE").MustString(
@ -360,7 +349,6 @@ func RestartLogsWithPIDSuffix() {
// NewLogServices creates all the log services
func NewLogServices(disableConsole bool) {
newLogService()
newMacaronLogService()
newRouterLogService()
newAccessLogService()
NewXORMLogService(disableConsole)

@ -41,7 +41,7 @@ var (
func newSessionService() {
sec := Cfg.Section("session")
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")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)

@ -12,6 +12,8 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/unrolled/render"
)
// Vars represents variables to be render in golang templates
@ -80,3 +82,15 @@ func getDirAssetNames(dir string) []string {
}
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(),
})
}

@ -18,8 +18,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
)
var (
@ -46,29 +44,6 @@ func GetAssetNames() []string {
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.
func Mailer() (*texttmpl.Template, *template.Template) {
for _, funcs := range NewTextFuncMap() {

@ -7,10 +7,7 @@
package templates
import (
"bytes"
"fmt"
"html/template"
"io"
"io/ioutil"
"os"
"path"
@ -21,8 +18,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"gitea.com/macaron/macaron"
)
var (
@ -30,24 +25,6 @@ var (
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
func GetAsset(name string) ([]byte, error) {
bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name))
@ -72,95 +49,6 @@ func GetAssetNames() []string {
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.
func Mailer() (*texttmpl.Template, *template.Template) {
for _, funcs := range NewTextFuncMap() {

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

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

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

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

@ -9,8 +9,8 @@ import (
"net/http/httptest"
"testing"
"gitea.com/macaron/binding"
"gitea.com/macaron/macaron"
"gitea.com/go-chi/binding"
"github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
)
@ -34,9 +34,10 @@ type (
func performValidationTest(t *testing.T, testCase validationTestCase) {
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
if actual == nil {
actual = binding.Errors{}
@ -49,7 +50,7 @@ func performValidationTest(t *testing.T, testCase validationTestCase) {
if err != nil {
panic(err)
}
req.Header.Add("Content-Type", "x-www-form-urlencoded")
m.ServeHTTP(httpRecorder, req)
switch httpRecorder.Code {

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

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

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

@ -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
}

@ -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)
}

@ -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.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.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.
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.
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.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.

@ -16,20 +16,20 @@ import (
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/cron"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/mailer"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
"gitea.com/go-chi/session"
)
const (
@ -132,7 +132,8 @@ func Dashboard(ctx *context.Context) {
}
// 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["PageIsAdmin"] = true
ctx.Data["PageIsAdminDashboard"] = true
@ -239,7 +240,7 @@ func Config(ctx *context.Context) {
ctx.Data["OfflineMode"] = setting.OfflineMode
ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
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 {
ctx.Data["GitVersion"] = version.Original()
}

@ -10,15 +10,16 @@ import (
"regexp"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"xorm.io/xorm/convert"
)
@ -206,7 +207,8 @@ func parseSSPIConfig(ctx *context.Context, form auth.AuthenticationForm) (*model
}
// 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["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true
@ -312,7 +314,8 @@ func EditAuthSource(ctx *context.Context) {
}
// 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["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true

@ -11,12 +11,13 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers"
router_user_setting "code.gitea.io/gitea/routers/user/setting"
"code.gitea.io/gitea/services/mailer"
@ -63,7 +64,8 @@ func NewUser(ctx *context.Context) {
}
// 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["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true
@ -214,7 +216,8 @@ func EditUser(ctx *context.Context) {
}
// 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["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true

@ -8,8 +8,9 @@ import (
"testing"
"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/web"
"github.com/stretchr/testify/assert"
)
@ -39,7 +40,8 @@ func TestNewUserPost_MustChangePassword(t *testing.T) {
MustChangePassword: true,
}
NewUserPost(ctx, form)
web.SetForm(ctx, &form)
NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.SuccessMsg)
@ -76,7 +78,8 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
MustChangePassword: false,
}
NewUserPost(ctx, form)
web.SetForm(ctx, &form)
NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.SuccessMsg)
@ -113,7 +116,8 @@ func TestNewUserPost_InvalidEmail(t *testing.T) {
MustChangePassword: false,
}
NewUserPost(ctx, form)
web.SetForm(ctx, &form)
NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
}

@ -13,12 +13,13 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
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/utils"
)
// 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
// ---
// summary: Create an organization
@ -43,7 +44,7 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateOrgOption)
u := user.GetUserByParams(ctx)
if ctx.Written() {
return

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

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
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/utils"
"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
func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
func CreateUser(ctx *context.APIContext) {
// swagger:operation POST /admin/users admin adminCreateUser
// ---
// summary: Create a user
@ -64,7 +65,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateUserOption)
u := &models.User{
Name: form.Username,
FullName: form.FullName,
@ -119,7 +120,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
}
// 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
// ---
// summary: Edit an existing user
@ -144,7 +145,7 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditUserOption)
u := user.GetUserByParams(ctx)
if ctx.Written() {
return
@ -283,7 +284,7 @@ func DeleteUser(ctx *context.APIContext) {
}
// 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
// ---
// 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"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateKeyOption)
u := user.GetUserByParams(ctx)
if ctx.Written() {
return
}
user.CreateUserPublicKey(ctx, form, u.ID)
user.CreateUserPublicKey(ctx, *form, u.ID)
}
// DeleteUserPublicKey api for deleting a user's public key

@ -66,14 +66,16 @@ package v1
import (
"net/http"
"reflect"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
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/misc"
"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/user"
"gitea.com/macaron/binding"
"gitea.com/macaron/macaron"
"gitea.com/go-chi/binding"
"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) {
sudo := ctx.Query("sudo")
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) {
userName := ctx.Params(":username")
repoName := ctx.Params(":reponame")
userName := ctx.Params("username")
repoName := ctx.Params("reponame")
var (
owner *models.User
@ -184,7 +187,7 @@ func repoAssignment() macaron.Handler {
}
// 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) {
if true == ctx.Data["IsApiToken"] {
return
@ -201,7 +204,7 @@ func reqToken() macaron.Handler {
}
}
func reqBasicAuth() macaron.Handler {
func reqBasicAuth() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Context.IsBasicAuth {
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "basic auth required")
@ -212,7 +215,7 @@ func reqBasicAuth() macaron.Handler {
}
// reqSiteAdmin user should be the site admin
func reqSiteAdmin() macaron.Handler {
func reqSiteAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() {
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.
func reqOwner() macaron.Handler {
func reqOwner() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
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
func reqAdmin() macaron.Handler {
func reqAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
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
func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
func reqRepoWriter(unitTypes ...models.UnitType) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
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
func reqRepoReader(unitType models.UnitType) macaron.Handler {
func reqRepoReader(unitType models.UnitType) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
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")
@ -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
func reqAnyRepoReader() macaron.Handler {
func reqAnyRepoReader() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
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
func reqOrgOwnership() macaron.Handler {
func reqOrgOwnership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() {
return
@ -304,7 +307,7 @@ func reqOrgOwnership() macaron.Handler {
}
// 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) {
if ctx.Context.IsUserSiteAdmin() {
return
@ -341,7 +344,7 @@ func reqTeamMembership() macaron.Handler {
}
// 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) {
if ctx.Context.IsUserSiteAdmin() {
return
@ -371,7 +374,7 @@ func reqOrgMembership() macaron.Handler {
}
}
func reqGitHook() macaron.Handler {
func reqGitHook() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.User.CanEditGitHook() {
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 (
assignOrg 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) {
if ctx.Repo.Repository.IsArchived {
ctx.NotFound()
@ -514,18 +510,59 @@ func mustNotBeArchived(ctx *context.APIContext) {
}
}
// RegisterRoutes registers all v1 APIs routes to web application.
func RegisterRoutes(m *macaron.Macaron) {
bind := binding.Bind
// bind binding an obj to a func(ctx *context.APIContext)
func bind(obj interface{}) http.HandlerFunc {
var tp = reflect.TypeOf(obj)
for tp.Kind() == reflect.Ptr {
tp = tp.Elem()
}
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)
})
}
if setting.API.EnableSwagger {
m.Get("/swagger", misc.Swagger) // Render V1 by default
// 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("/v1", func() {
m.Group("", func() {
// Miscellaneous
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("/signing-key.gpg", misc.SigningKey)
@ -544,7 +581,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Get(notify.ListNotifications).
Put(notify.ReadNotifications)
m.Get("/new", notify.NewAvailable)
m.Combo("/threads/:id").
m.Combo("/threads/{id}").
Get(notify.GetThread).
Patch(notify.ReadThread)
}, reqToken())
@ -553,28 +590,31 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/users", func() {
m.Get("/search", user.Search)
m.Group("/:username", func() {
m.Group("/{username}", func() {
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.Group("/tokens", func() {
m.Combo("").Get(user.ListAccessTokens).
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
m.Combo("/:id").Delete(user.DeleteAccessToken)
m.Combo("/{id}").Delete(user.DeleteAccessToken)
}, reqBasicAuth())
})
})
m.Group("/users", func() {
m.Group("/:username", func() {
m.Group("/{username}", func() {
m.Get("/keys", user.ListPublicKeys)
m.Get("/gpg_keys", user.ListGPGKeys)
m.Get("/followers", user.ListFollowers)
m.Group("/following", func() {
m.Get("", user.ListFollowing)
m.Get("/:target", user.CheckFollowing)
m.Get("/{target}", user.CheckFollowing)
})
m.Get("/starred", user.GetStarredRepos)
@ -592,20 +632,20 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/followers", user.ListMyFollowers)
m.Group("/following", func() {
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.Combo("").Get(user.ListMyPublicKeys).
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
m.Combo("/:id").Get(user.GetPublicKey).
m.Combo("/{id}").Get(user.GetPublicKey).
Delete(user.DeletePublicKey)
})
m.Group("/applications", func() {
m.Combo("/oauth2").
Get(user.ListOauth2Applications).
Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
m.Combo("/oauth2/:id").
m.Combo("/oauth2/{id}").
Delete(user.DeleteOauth2Application).
Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
Get(user.GetOauth2Application)
@ -614,7 +654,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/gpg_keys", func() {
m.Combo("").Get(user.ListMyGPGKeys).
Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
m.Combo("/:id").Get(user.GetGPGKey).
m.Combo("/{id}").Get(user.GetGPGKey).
Delete(user.DeleteGPGKey)
})
@ -623,7 +663,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/starred", func() {
m.Get("", user.GetMyStarredRepos)
m.Group("/:username/:reponame", func() {
m.Group("/{username}/{reponame}", func() {
m.Get("", user.IsStarring)
m.Put("", user.Star)
m.Delete("", user.Unstar)
@ -639,9 +679,9 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqToken())
// 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.Get("/search", repo.Search)
@ -650,10 +690,10 @@ func RegisterRoutes(m *macaron.Macaron) {
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).
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.Combo("/notifications").
Get(reqToken(), notify.ListRepoNotifications).
@ -661,15 +701,15 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/hooks", func() {
m.Combo("").Get(repo.ListHooks).
Post(bind(api.CreateHookOption{}), repo.CreateHook)
m.Group("/:id", func() {
m.Group("/{id}", func() {
m.Combo("").Get(repo.GetHook).
Patch(bind(api.EditHookOption{}), repo.EditHook).
Delete(repo.DeleteHook)
m.Post("/tests", context.RepoRefForAPI(), repo.TestHook)
m.Post("/tests", context.RepoRefForAPI, repo.TestHook)
})
m.Group("/git", func() {
m.Combo("").Get(repo.ListGitHooks)
m.Group("/:id", func() {
m.Group("/{id}", func() {
m.Combo("").Get(repo.GetGitHook).
Patch(bind(api.EditGitHookOption{}), repo.EditGitHook).
Delete(repo.DeleteGitHook)
@ -678,11 +718,11 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqToken(), reqAdmin())
m.Group("/collaborators", func() {
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).
Delete(reqAdmin(), repo.DeleteCollaborator)
}, 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.Combo("/forks").Get(repo.ListForks).
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.Get("", repo.ListBranchProtections)
m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
m.Group("/:name", func() {
m.Group("/{name}", func() {
m.Get("", repo.GetBranchProtection)
m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
m.Delete("", repo.DeleteBranchProtection)
@ -707,19 +747,19 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys).
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
m.Combo("/:id").Get(repo.GetDeployKey).
m.Combo("/{id}").Get(repo.GetDeployKey).
Delete(repo.DeleteDeploykey)
}, reqToken(), reqAdmin())
m.Group("/times", func() {
m.Combo("").Get(repo.ListTrackedTimesByRepository)
m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
}, mustEnableIssues, reqToken())
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Group("/comments", func() {
m.Get("", repo.ListRepoIssueComments)
m.Group("/:id", func() {
m.Group("/{id}", func() {
m.Combo("").
Get(repo.GetIssueComment).
Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
@ -730,13 +770,13 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
})
})
m.Group("/:index", func() {
m.Group("/{index}", func() {
m.Combo("").Get(repo.GetIssue).
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
m.Group("/comments", func() {
m.Combo("").Get(repo.ListIssueComments).
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)
})
m.Group("/labels", func() {
@ -744,14 +784,14 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
Delete(reqToken(), repo.ClearIssueLabels)
m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel)
})
m.Group("/times", func() {
m.Combo("").
Get(repo.ListTrackedTimes).
Post(bind(api.AddTimeOption{}), repo.AddTime).
Delete(repo.ResetIssueTime)
m.Delete("/:id", repo.DeleteTime)
m.Delete("/{id}", repo.DeleteTime)
}, reqToken())
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
m.Group("/stopwatch", func() {
@ -762,8 +802,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/subscriptions", func() {
m.Get("", repo.GetIssueSubscribers)
m.Get("/check", reqToken(), repo.CheckIssueSubscription)
m.Put("/:user", reqToken(), repo.AddIssueSubscription)
m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
m.Put("/{user}", reqToken(), repo.AddIssueSubscription)
m.Delete("/{user}", reqToken(), repo.DelIssueSubscription)
})
m.Combo("/reactions").
Get(repo.GetIssueReactions).
@ -774,7 +814,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/labels", func() {
m.Combo("").Get(repo.ListLabels).
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).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteLabel)
})
@ -783,7 +823,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/milestones", func() {
m.Combo("").Get(repo.ListMilestones).
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).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone)
})
@ -797,30 +837,30 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases).
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).
Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.EditReleaseOption{}), repo.EditRelease).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteRelease)
m.Group("/assets", func() {
m.Combo("").Get(repo.ListReleaseAttachments).
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).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment)
})
})
m.Group("/tags", func() {
m.Combo("/:tag").
m.Combo("/{tag}").
Get(repo.GetReleaseTag).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
})
}, reqRepoReader(models.UnitTypeReleases))
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.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).
m.Combo("").Get(repo.ListPullRequests).
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
m.Group("/:index", func() {
m.Group("/{index}", func() {
m.Combo("").Get(repo.GetPullRequest).
Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
m.Get(".diff", repo.DownloadPullDiff)
@ -832,7 +872,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("").
Get(repo.ListPullReviews).
Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
m.Group("/:id", func() {
m.Group("/{id}", func() {
m.Combo("").
Get(repo.GetPullReview).
Delete(reqToken(), repo.DeletePullReview).
@ -847,25 +887,25 @@ func RegisterRoutes(m *macaron.Macaron) {
})
}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
m.Group("/statuses", func() {
m.Combo("/:sha").Get(repo.GetCommitStatuses).
m.Combo("/{sha}").Get(repo.GetCommitStatuses).
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/commits", func() {
m.Get("", repo.GetAllCommits)
m.Group("/:ref", func() {
m.Group("/{ref}", func() {
m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef)
})
}, reqRepoReader(models.UnitTypeCode))
m.Group("/git", func() {
m.Group("/commits", func() {
m.Get("/:sha", repo.GetSingleCommit)
m.Get("/{sha}", repo.GetSingleCommit)
})
m.Get("/refs", repo.GetGitAllRefs)
m.Get("/refs/*", repo.GetGitRefs)
m.Get("/trees/:sha", context.RepoRefForAPI(), repo.GetTree)
m.Get("/blobs/:sha", context.RepoRefForAPI(), repo.GetBlob)
m.Get("/tags/:sha", context.RepoRefForAPI(), repo.GetTag)
m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree)
m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob)
m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetTag)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
@ -880,7 +920,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
m.Group("/:topic", func() {
m.Group("/{topic}", func() {
m.Combo("").Put(reqToken(), repo.AddTopic).
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
@ -892,10 +932,10 @@ func RegisterRoutes(m *macaron.Macaron) {
// Organizations
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.Get("/orgs", org.GetAll)
m.Group("/orgs/:org", func() {
m.Group("/orgs/{org}", func() {
m.Combo("").Get(org.Get).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
Delete(reqToken(), reqOrgOwnership(), org.Delete)
@ -903,12 +943,12 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
m.Group("/members", func() {
m.Get("", org.ListMembers)
m.Combo("/:username").Get(org.IsMember).
m.Combo("/{username}").Get(org.IsMember).
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
})
m.Group("/public_members", func() {
m.Get("", org.ListPublicMembers)
m.Combo("/:username").Get(org.IsPublicMember).
m.Combo("/{username}").Get(org.IsPublicMember).
Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
})
@ -920,56 +960,52 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/labels", func() {
m.Get("", org.ListLabels)
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).
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
})
m.Group("/hooks", func() {
m.Combo("").Get(org.ListHooks).
Post(bind(api.CreateHookOption{}), org.CreateHook)
m.Combo("/:id").Get(org.GetHook).
m.Combo("/{id}").Get(org.GetHook).
Patch(bind(api.EditHookOption{}), org.EditHook).
Delete(org.DeleteHook)
}, reqToken(), reqOrgOwnership())
}, orgAssignment(true))
m.Group("/teams/:teamid", func() {
m.Group("/teams/{teamid}", func() {
m.Combo("").Get(org.GetTeam).
Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
Delete(reqOrgOwnership(), org.DeleteTeam)
m.Group("/members", func() {
m.Get("", org.GetTeamMembers)
m.Combo("/:username").
m.Combo("/{username}").
Get(org.GetTeamMember).
Put(reqOrgOwnership(), org.AddTeamMember).
Delete(reqOrgOwnership(), org.RemoveTeamMember)
})
m.Group("/repos", func() {
m.Get("", org.GetTeamRepos)
m.Combo("/:org/:reponame").
m.Combo("/{org}/{reponame}").
Put(org.AddTeamRepository).
Delete(org.RemoveTeamRepository)
})
}, orgAssignment(false, true), reqToken(), reqTeamMembership())
m.Any("/*", func(ctx *context.APIContext) {
ctx.NotFound()
})
m.Group("/admin", func() {
m.Group("/cron", func() {
m.Get("", admin.ListCronTasks)
m.Post("/:task", admin.PostCronTask)
m.Post("/{task}", admin.PostCronTask)
})
m.Get("/orgs", admin.GetAllOrgs)
m.Group("/users", func() {
m.Get("", admin.GetAllUsers)
m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
m.Group("/:username", func() {
m.Group("/{username}", func() {
m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
Delete(admin.DeleteUser)
m.Group("/keys", func() {
m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
m.Delete("/:id", admin.DeleteUserPublicKey)
m.Delete("/{id}", admin.DeleteUserPublicKey)
})
m.Get("/orgs", org.ListUserOrgs)
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
@ -978,23 +1014,26 @@ func RegisterRoutes(m *macaron.Macaron) {
})
m.Group("/unadopted", func() {
m.Get("", admin.ListUnadoptedRepositories)
m.Post("/:username/:reponame", admin.AdoptRepository)
m.Delete("/:username/:reponame", admin.DeleteUnadoptedRepository)
m.Post("/{username}/{reponame}", admin.AdoptRepository)
m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
})
}, reqToken(), reqSiteAdmin())
m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch)
})
}, securityHeaders(), context.APIContexter(), sudo())
}, sudo())
return m
}
func securityHeaders() macaron.Handler {
return func(ctx *macaron.Context) {
ctx.Resp.Before(func(w macaron.ResponseWriter) {
func securityHeaders() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
// 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)
})
}
}

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

@ -15,10 +15,10 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates"
"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"
)
@ -26,25 +26,21 @@ const AppURL = "http://localhost:3000/"
const Repo = "gogits/gogs"
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()
c := &macaron.Context{
Injector: inject.New(),
Req: macaron.Request{Request: req},
Resp: macaron.NewResponseWriter(req.Method, resp),
Render: &macaron.DummyRender{ResponseWriter: resp},
Data: make(map[string]interface{}),
c := &context.Context{
Req: req,
Resp: context.NewResponse(resp),
Render: rnd,
Data: make(map[string]interface{}),
}
c.Map(c)
c.Map(req)
return c, resp
}
func wrap(ctx *macaron.Context) *context.APIContext {
func wrap(ctx *context.Context) *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 {
options.Text = testCases[i]
Markdown(ctx, options)
web.SetForm(ctx, &options)
Markdown(ctx)
assert.Equal(t, testCases[i+1], resp.Body.String())
resp.Body.Reset()
}
@ -156,7 +153,8 @@ func TestAPI_RenderSimple(t *testing.T) {
for i := 0; i < len(simpleCases); i += 2 {
options.Text = simpleCases[i]
Markdown(ctx, options)
web.SetForm(ctx, &options)
Markdown(ctx)
assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset()
}
@ -174,7 +172,7 @@ func TestAPI_RenderRaw(t *testing.T) {
ctx := wrap(m)
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)
assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset()

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

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

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

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
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/utils"
)
@ -131,7 +132,7 @@ func GetTeam(ctx *context.APIContext) {
}
// 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
// ---
// summary: Create a team
@ -154,7 +155,7 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
// "$ref": "#/responses/Team"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateTeamOption)
team := &models.Team{
OrgID: ctx.Org.Organization.ID,
Name: form.Name,
@ -190,7 +191,7 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
}
// 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
// ---
// summary: Edit a team
@ -212,6 +213,8 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
// "200":
// "$ref": "#/responses/Team"
form := web.GetForm(ctx).(*api.EditTeamOption)
team := ctx.Org.Team
if err := team.GetUnits(); err != nil {
ctx.InternalServerError(err)

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull"
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
func CreateBranch(ctx *context.APIContext, opt api.CreateBranchRepoOption) {
func CreateBranch(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
// ---
// summary: Create a branch
@ -206,6 +207,7 @@ func CreateBranch(ctx *context.APIContext, opt api.CreateBranchRepoOption) {
// "409":
// description: The branch with the same name already exists.
opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
return
@ -395,7 +397,7 @@ func ListBranchProtections(ctx *context.APIContext) {
}
// 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
// ---
// summary: Create a branch protections for a repository
@ -428,6 +430,7 @@ func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtec
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
repo := ctx.Repo.Repository
// 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
func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtectionOption) {
func EditBranchProtection(ctx *context.APIContext) {
// 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
@ -596,7 +599,7 @@ func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtection
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
repo := ctx.Repo.Repository
bpName := ctx.Params(":name")
protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)

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

@ -69,7 +69,11 @@ func getCommit(ctx *context.APIContext, identifier string) {
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(identifier)
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
}

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/repo"
)
@ -167,7 +168,7 @@ func canReadFiles(r *context.Repository) bool {
}
// 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
// ---
// summary: Create a file in a repository
@ -206,6 +207,7 @@ func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
// "422":
// "$ref": "#/responses/error"
apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
if ctx.Repo.Repository.IsEmpty {
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
func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
func UpdateFile(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
// ---
// summary: Update a file in a repository
@ -291,7 +293,7 @@ func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/error"
apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
if ctx.Repo.Repository.IsEmpty {
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
func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
func DeleteFile(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
// ---
// summary: Delete a file in a repository
@ -416,6 +418,7 @@ func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
// "404":
// "$ref": "#/responses/error"
apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
if !canWriteFiles(ctx.Repo) {
ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID,

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

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

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
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/services/webhook"
)
@ -158,7 +159,7 @@ func TestHook(ctx *context.APIContext) {
}
// 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
// ---
// summary: Create a hook
@ -184,14 +185,16 @@ func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
// responses:
// "201":
// "$ref": "#/responses/Hook"
if !utils.CheckCreateHookOption(ctx, &form) {
form := web.GetForm(ctx).(*api.CreateHookOption)
if !utils.CheckCreateHookOption(ctx, form) {
return
}
utils.AddRepoHook(ctx, &form)
utils.AddRepoHook(ctx, form)
}
// 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
// ---
// summary: Edit a hook in a repository
@ -221,8 +224,9 @@ func EditHook(ctx *context.APIContext, form api.EditHookOption) {
// responses:
// "200":
// "$ref": "#/responses/Hook"
form := web.GetForm(ctx).(*api.EditHookOption)
hookID := ctx.ParamsInt64(":id")
utils.EditRepoHook(ctx, &form, hookID)
utils.EditRepoHook(ctx, form, hookID)
}
// DeleteHook delete a hook of a repository

@ -22,6 +22,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
issue_service "code.gitea.io/gitea/services/issue"
)
@ -448,7 +449,7 @@ func GetIssue(ctx *context.APIContext) {
}
// 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
// ---
// 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"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateIssueOption)
var deadlineUnix timeutil.TimeStamp
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
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
func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
func EditIssue(ctx *context.APIContext) {
// 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.
@ -603,6 +604,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
// "412":
// "$ref": "#/responses/error"
form := web.GetForm(ctx).(*api.EditIssueOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
@ -723,7 +725,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
// 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
// ---
// 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"
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditDeadlineOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {

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

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
issue_service "code.gitea.io/gitea/services/issue"
)
@ -64,7 +65,7 @@ func ListIssueLabels(ctx *context.APIContext) {
}
// 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
// ---
// summary: Add a label to an issue
@ -99,7 +100,8 @@ func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// "403":
// "$ref": "#/responses/forbidden"
issue, labels, err := prepareForReplaceOrAdd(ctx, form)
form := web.GetForm(ctx).(*api.IssueLabelsOption)
issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
if err != nil {
return
}
@ -190,7 +192,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
}
// 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
// ---
// summary: Replace an issue's labels
@ -224,8 +226,8 @@ func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
// "$ref": "#/responses/LabelList"
// "403":
// "$ref": "#/responses/forbidden"
issue, labels, err := prepareForReplaceOrAdd(ctx, form)
form := web.GetForm(ctx).(*api.IssueLabelsOption)
issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
if err != nil {
return
}

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"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
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
// ---
// summary: Add a reaction to a comment of an issue
@ -127,11 +128,13 @@ func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOpti
// "403":
// "$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
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
// ---
// summary: Remove a reaction from a comment of an issue
@ -166,7 +169,9 @@ func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
// "403":
// "$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) {
@ -304,7 +309,7 @@ func GetIssueReactions(ctx *context.APIContext) {
}
// 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
// ---
// summary: Add a reaction to an issue
@ -340,12 +345,12 @@ func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
// "$ref": "#/responses/Reaction"
// "403":
// "$ref": "#/responses/forbidden"
changeIssueReaction(ctx, form, true)
form := web.GetForm(ctx).(*api.EditReactionOption)
changeIssueReaction(ctx, *form, true)
}
// 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
// ---
// summary: Remove a reaction from an issue
@ -379,8 +384,8 @@ func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
changeIssueReaction(ctx, form, false)
form := web.GetForm(ctx).(*api.EditReactionOption)
changeIssueReaction(ctx, *form, false)
}
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {

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

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

Loading…
Cancel
Save