You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

middleware.go 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "net/http"
  6. "strings"
  7. user_model "code.gitea.io/gitea/models/user"
  8. "code.gitea.io/gitea/modules/context"
  9. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/setting"
  11. "code.gitea.io/gitea/modules/web/middleware"
  12. )
  13. // Auth is a middleware to authenticate a web user
  14. func Auth(authMethod Method) func(*context.Context) {
  15. return func(ctx *context.Context) {
  16. ar, err := authShared(ctx.Base, ctx.Session, authMethod)
  17. if err != nil {
  18. log.Error("Failed to verify user: %v", err)
  19. ctx.Error(http.StatusUnauthorized, "Verify")
  20. return
  21. }
  22. ctx.Doer = ar.Doer
  23. ctx.IsSigned = ar.Doer != nil
  24. ctx.IsBasicAuth = ar.IsBasicAuth
  25. if ctx.Doer == nil {
  26. // ensure the session uid is deleted
  27. _ = ctx.Session.Delete("uid")
  28. }
  29. }
  30. }
  31. // APIAuth is a middleware to authenticate an api user
  32. func APIAuth(authMethod Method) func(*context.APIContext) {
  33. return func(ctx *context.APIContext) {
  34. ar, err := authShared(ctx.Base, nil, authMethod)
  35. if err != nil {
  36. ctx.Error(http.StatusUnauthorized, "APIAuth", err)
  37. return
  38. }
  39. ctx.Doer = ar.Doer
  40. ctx.IsSigned = ar.Doer != nil
  41. ctx.IsBasicAuth = ar.IsBasicAuth
  42. }
  43. }
  44. type authResult struct {
  45. Doer *user_model.User
  46. IsBasicAuth bool
  47. }
  48. func authShared(ctx *context.Base, sessionStore SessionStore, authMethod Method) (ar authResult, err error) {
  49. ar.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, sessionStore)
  50. if err != nil {
  51. return ar, err
  52. }
  53. if ar.Doer != nil {
  54. if ctx.Locale.Language() != ar.Doer.Language {
  55. ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
  56. }
  57. ar.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == BasicMethodName
  58. ctx.Data["IsSigned"] = true
  59. ctx.Data[middleware.ContextDataKeySignedUser] = ar.Doer
  60. ctx.Data["SignedUserID"] = ar.Doer.ID
  61. ctx.Data["IsAdmin"] = ar.Doer.IsAdmin
  62. } else {
  63. ctx.Data["SignedUserID"] = int64(0)
  64. }
  65. return ar, nil
  66. }
  67. // VerifyOptions contains required or check options
  68. type VerifyOptions struct {
  69. SignInRequired bool
  70. SignOutRequired bool
  71. AdminRequired bool
  72. DisableCSRF bool
  73. }
  74. // VerifyAuthWithOptions checks authentication according to options
  75. func VerifyAuthWithOptions(options *VerifyOptions) func(ctx *context.Context) {
  76. return func(ctx *context.Context) {
  77. // Check prohibit login users.
  78. if ctx.IsSigned {
  79. if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
  80. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  81. ctx.HTML(http.StatusOK, "user/auth/activate")
  82. return
  83. }
  84. if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
  85. log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
  86. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  87. ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
  88. return
  89. }
  90. if ctx.Doer.MustChangePassword {
  91. if ctx.Req.URL.Path != "/user/settings/change_password" {
  92. if strings.HasPrefix(ctx.Req.UserAgent(), "git") {
  93. ctx.Error(http.StatusUnauthorized, ctx.Tr("auth.must_change_password"))
  94. return
  95. }
  96. ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
  97. ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
  98. if ctx.Req.URL.Path != "/user/events" {
  99. middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
  100. }
  101. ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
  102. return
  103. }
  104. } else if ctx.Req.URL.Path == "/user/settings/change_password" {
  105. // make sure that the form cannot be accessed by users who don't need this
  106. ctx.Redirect(setting.AppSubURL + "/")
  107. return
  108. }
  109. }
  110. // Redirect to dashboard if user tries to visit any non-login page.
  111. if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
  112. ctx.Redirect(setting.AppSubURL + "/")
  113. return
  114. }
  115. if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
  116. ctx.Csrf.Validate(ctx)
  117. if ctx.Written() {
  118. return
  119. }
  120. }
  121. if options.SignInRequired {
  122. if !ctx.IsSigned {
  123. if ctx.Req.URL.Path != "/user/events" {
  124. middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
  125. }
  126. ctx.Redirect(setting.AppSubURL + "/user/login")
  127. return
  128. } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
  129. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  130. ctx.HTML(http.StatusOK, "user/auth/activate")
  131. return
  132. }
  133. }
  134. // Redirect to log in page if auto-signin info is provided and has not signed in.
  135. if !options.SignOutRequired && !ctx.IsSigned &&
  136. len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 {
  137. if ctx.Req.URL.Path != "/user/events" {
  138. middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
  139. }
  140. ctx.Redirect(setting.AppSubURL + "/user/login")
  141. return
  142. }
  143. if options.AdminRequired {
  144. if !ctx.Doer.IsAdmin {
  145. ctx.Error(http.StatusForbidden)
  146. return
  147. }
  148. ctx.Data["PageIsAdmin"] = true
  149. }
  150. }
  151. }
  152. // VerifyAuthWithOptionsAPI checks authentication according to options
  153. func VerifyAuthWithOptionsAPI(options *VerifyOptions) func(ctx *context.APIContext) {
  154. return func(ctx *context.APIContext) {
  155. // Check prohibit login users.
  156. if ctx.IsSigned {
  157. if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
  158. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  159. ctx.JSON(http.StatusForbidden, map[string]string{
  160. "message": "This account is not activated.",
  161. })
  162. return
  163. }
  164. if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
  165. log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
  166. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  167. ctx.JSON(http.StatusForbidden, map[string]string{
  168. "message": "This account is prohibited from signing in, please contact your site administrator.",
  169. })
  170. return
  171. }
  172. if ctx.Doer.MustChangePassword {
  173. ctx.JSON(http.StatusForbidden, map[string]string{
  174. "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
  175. })
  176. return
  177. }
  178. }
  179. // Redirect to dashboard if user tries to visit any non-login page.
  180. if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
  181. ctx.Redirect(setting.AppSubURL + "/")
  182. return
  183. }
  184. if options.SignInRequired {
  185. if !ctx.IsSigned {
  186. // Restrict API calls with error message.
  187. ctx.JSON(http.StatusForbidden, map[string]string{
  188. "message": "Only signed in user is allowed to call APIs.",
  189. })
  190. return
  191. } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
  192. ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
  193. ctx.JSON(http.StatusForbidden, map[string]string{
  194. "message": "This account is not activated.",
  195. })
  196. return
  197. }
  198. }
  199. if options.AdminRequired {
  200. if !ctx.Doer.IsAdmin {
  201. ctx.JSON(http.StatusForbidden, map[string]string{
  202. "message": "You have no permission to request for this.",
  203. })
  204. return
  205. }
  206. }
  207. }
  208. }