gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package security
  5. import (
  6. "bytes"
  7. "encoding/base64"
  8. "html/template"
  9. "image/png"
  10. "net/http"
  11. "strings"
  12. "code.gitea.io/gitea/models/auth"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/session"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/web"
  18. "code.gitea.io/gitea/services/context"
  19. "code.gitea.io/gitea/services/forms"
  20. "github.com/pquerna/otp"
  21. "github.com/pquerna/otp/totp"
  22. )
  23. // RegenerateScratchTwoFactor regenerates the user's 2FA scratch code.
  24. func RegenerateScratchTwoFactor(ctx *context.Context) {
  25. if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
  26. ctx.HTTPError(http.StatusNotFound)
  27. return
  28. }
  29. ctx.Data["Title"] = ctx.Tr("settings")
  30. ctx.Data["PageIsSettingsSecurity"] = true
  31. t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
  32. if err != nil {
  33. if auth.IsErrTwoFactorNotEnrolled(err) {
  34. ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
  35. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  36. } else {
  37. ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
  38. }
  39. return
  40. }
  41. token, err := t.GenerateScratchToken()
  42. if err != nil {
  43. ctx.ServerError("SettingsTwoFactor: Failed to GenerateScratchToken", err)
  44. return
  45. }
  46. if err = auth.UpdateTwoFactor(ctx, t); err != nil {
  47. ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err)
  48. return
  49. }
  50. ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token))
  51. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  52. }
  53. // DisableTwoFactor deletes the user's 2FA settings.
  54. func DisableTwoFactor(ctx *context.Context) {
  55. if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
  56. ctx.HTTPError(http.StatusNotFound)
  57. return
  58. }
  59. ctx.Data["Title"] = ctx.Tr("settings")
  60. ctx.Data["PageIsSettingsSecurity"] = true
  61. t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
  62. if err != nil {
  63. if auth.IsErrTwoFactorNotEnrolled(err) {
  64. ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
  65. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  66. } else {
  67. ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
  68. }
  69. return
  70. }
  71. if err = auth.DeleteTwoFactorByID(ctx, t.ID, ctx.Doer.ID); err != nil {
  72. if auth.IsErrTwoFactorNotEnrolled(err) {
  73. // There is a potential DB race here - we must have been disabled by another request in the intervening period
  74. ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
  75. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  76. } else {
  77. ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
  78. }
  79. return
  80. }
  81. ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
  82. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  83. }
  84. func twofaGenerateSecretAndQr(ctx *context.Context) bool {
  85. var otpKey *otp.Key
  86. var err error
  87. uri := ctx.Session.Get("twofaUri")
  88. if uri != nil {
  89. otpKey, err = otp.NewKeyFromURL(uri.(string))
  90. if err != nil {
  91. ctx.ServerError("SettingsTwoFactor: Failed NewKeyFromURL: ", err)
  92. return false
  93. }
  94. }
  95. // Filter unsafe character ':' in issuer
  96. issuer := strings.ReplaceAll(setting.AppName+" ("+setting.Domain+")", ":", "")
  97. if otpKey == nil {
  98. otpKey, err = totp.Generate(totp.GenerateOpts{
  99. SecretSize: 40,
  100. Issuer: issuer,
  101. AccountName: ctx.Doer.Name,
  102. })
  103. if err != nil {
  104. ctx.ServerError("SettingsTwoFactor: totpGenerate Failed", err)
  105. return false
  106. }
  107. }
  108. ctx.Data["TwofaSecret"] = otpKey.Secret()
  109. img, err := otpKey.Image(320, 240)
  110. if err != nil {
  111. ctx.ServerError("SettingsTwoFactor: otpKey image generation failed", err)
  112. return false
  113. }
  114. var imgBytes bytes.Buffer
  115. if err = png.Encode(&imgBytes, img); err != nil {
  116. ctx.ServerError("SettingsTwoFactor: otpKey png encoding failed", err)
  117. return false
  118. }
  119. ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
  120. if err := ctx.Session.Set("twofaSecret", otpKey.Secret()); err != nil {
  121. ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaSecret", err)
  122. return false
  123. }
  124. if err := ctx.Session.Set("twofaUri", otpKey.String()); err != nil {
  125. ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaUri", err)
  126. return false
  127. }
  128. // Here we're just going to try to release the session early
  129. if err := ctx.Session.Release(); err != nil {
  130. // we'll tolerate errors here as they *should* get saved elsewhere
  131. log.Error("Unable to save changes to the session: %v", err)
  132. }
  133. return true
  134. }
  135. // EnrollTwoFactor shows the page where the user can enroll into 2FA.
  136. func EnrollTwoFactor(ctx *context.Context) {
  137. if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
  138. ctx.HTTPError(http.StatusNotFound)
  139. return
  140. }
  141. ctx.Data["Title"] = ctx.Tr("settings")
  142. ctx.Data["PageIsSettingsSecurity"] = true
  143. ctx.Data["ShowTwoFactorRequiredMessage"] = false
  144. t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
  145. if t != nil {
  146. // already enrolled - we should redirect back!
  147. log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.Doer)
  148. ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
  149. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  150. return
  151. }
  152. if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
  153. ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err)
  154. return
  155. }
  156. if !twofaGenerateSecretAndQr(ctx) {
  157. return
  158. }
  159. ctx.HTML(http.StatusOK, tplSettingsTwofaEnroll)
  160. }
  161. // EnrollTwoFactorPost handles enrolling the user into 2FA.
  162. func EnrollTwoFactorPost(ctx *context.Context) {
  163. if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageMFA) {
  164. ctx.HTTPError(http.StatusNotFound)
  165. return
  166. }
  167. form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
  168. ctx.Data["Title"] = ctx.Tr("settings")
  169. ctx.Data["PageIsSettingsSecurity"] = true
  170. ctx.Data["ShowTwoFactorRequiredMessage"] = false
  171. t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
  172. if t != nil {
  173. // already enrolled
  174. ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
  175. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  176. return
  177. }
  178. if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
  179. ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err)
  180. return
  181. }
  182. if ctx.HasError() {
  183. if !twofaGenerateSecretAndQr(ctx) {
  184. return
  185. }
  186. ctx.HTML(http.StatusOK, tplSettingsTwofaEnroll)
  187. return
  188. }
  189. secretRaw := ctx.Session.Get("twofaSecret")
  190. if secretRaw == nil {
  191. ctx.Flash.Error(ctx.Tr("settings.twofa_failed_get_secret"))
  192. ctx.Redirect(setting.AppSubURL + "/user/settings/security/two_factor/enroll")
  193. return
  194. }
  195. secret := secretRaw.(string)
  196. if !totp.Validate(form.Passcode, secret) {
  197. if !twofaGenerateSecretAndQr(ctx) {
  198. return
  199. }
  200. ctx.Flash.Error(ctx.Tr("settings.passcode_invalid"))
  201. ctx.Redirect(setting.AppSubURL + "/user/settings/security/two_factor/enroll")
  202. return
  203. }
  204. t = &auth.TwoFactor{
  205. UID: ctx.Doer.ID,
  206. }
  207. err = t.SetSecret(secret)
  208. if err != nil {
  209. ctx.ServerError("SettingsTwoFactor: Failed to set secret", err)
  210. return
  211. }
  212. token, err := t.GenerateScratchToken()
  213. if err != nil {
  214. ctx.ServerError("SettingsTwoFactor: Failed to generate scratch token", err)
  215. return
  216. }
  217. newTwoFactorErr := auth.NewTwoFactor(ctx, t)
  218. if newTwoFactorErr == nil {
  219. _ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
  220. }
  221. // Now we have to delete the secrets - because if we fail to insert then it's highly likely that they have already been used
  222. // If we can detect the unique constraint failure below we can move this to after the NewTwoFactor
  223. if err := ctx.Session.Delete("twofaSecret"); err != nil {
  224. // tolerate this failure - it's more important to continue
  225. log.Error("Unable to delete twofaSecret from the session: Error: %v", err)
  226. }
  227. if err := ctx.Session.Delete("twofaUri"); err != nil {
  228. // tolerate this failure - it's more important to continue
  229. log.Error("Unable to delete twofaUri from the session: Error: %v", err)
  230. }
  231. if err := ctx.Session.Release(); err != nil {
  232. // tolerate this failure - it's more important to continue
  233. log.Error("Unable to save changes to the session: %v", err)
  234. }
  235. if newTwoFactorErr != nil {
  236. // FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
  237. // If there is a unique constraint fail we should just tolerate the error
  238. ctx.ServerError("SettingsTwoFactor: Failed to save two factor", newTwoFactorErr)
  239. return
  240. }
  241. ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
  242. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  243. }