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.

openid.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. user_model "code.gitea.io/gitea/models/user"
  9. "code.gitea.io/gitea/modules/auth/openid"
  10. "code.gitea.io/gitea/modules/base"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/setting"
  14. "code.gitea.io/gitea/modules/util"
  15. "code.gitea.io/gitea/modules/web"
  16. "code.gitea.io/gitea/modules/web/middleware"
  17. "code.gitea.io/gitea/services/auth"
  18. "code.gitea.io/gitea/services/forms"
  19. )
  20. const (
  21. tplSignInOpenID base.TplName = "user/auth/signin_openid"
  22. tplConnectOID base.TplName = "user/auth/signup_openid_connect"
  23. tplSignUpOID base.TplName = "user/auth/signup_openid_register"
  24. )
  25. // SignInOpenID render sign in page
  26. func SignInOpenID(ctx *context.Context) {
  27. ctx.Data["Title"] = ctx.Tr("sign_in")
  28. if ctx.FormString("openid.return_to") != "" {
  29. signInOpenIDVerify(ctx)
  30. return
  31. }
  32. // Check auto-login.
  33. isSucceed, err := AutoSignIn(ctx)
  34. if err != nil {
  35. ctx.ServerError("AutoSignIn", err)
  36. return
  37. }
  38. redirectTo := ctx.FormString("redirect_to")
  39. if len(redirectTo) > 0 {
  40. middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
  41. } else {
  42. redirectTo = ctx.GetSiteCookie("redirect_to")
  43. }
  44. if isSucceed {
  45. middleware.DeleteRedirectToCookie(ctx.Resp)
  46. ctx.RedirectToFirst(redirectTo)
  47. return
  48. }
  49. ctx.Data["PageIsSignIn"] = true
  50. ctx.Data["PageIsLoginOpenID"] = true
  51. ctx.HTML(http.StatusOK, tplSignInOpenID)
  52. }
  53. // Check if the given OpenID URI is allowed by blacklist/whitelist
  54. func allowedOpenIDURI(uri string) (err error) {
  55. // In case a Whitelist is present, URI must be in it
  56. // in order to be accepted
  57. if len(setting.Service.OpenIDWhitelist) != 0 {
  58. for _, pat := range setting.Service.OpenIDWhitelist {
  59. if pat.MatchString(uri) {
  60. return nil // pass
  61. }
  62. }
  63. // must match one of this or be refused
  64. return fmt.Errorf("URI not allowed by whitelist")
  65. }
  66. // A blacklist match expliclty forbids
  67. for _, pat := range setting.Service.OpenIDBlacklist {
  68. if pat.MatchString(uri) {
  69. return fmt.Errorf("URI forbidden by blacklist")
  70. }
  71. }
  72. return nil
  73. }
  74. // SignInOpenIDPost response for openid sign in request
  75. func SignInOpenIDPost(ctx *context.Context) {
  76. form := web.GetForm(ctx).(*forms.SignInOpenIDForm)
  77. ctx.Data["Title"] = ctx.Tr("sign_in")
  78. ctx.Data["PageIsSignIn"] = true
  79. ctx.Data["PageIsLoginOpenID"] = true
  80. if ctx.HasError() {
  81. ctx.HTML(http.StatusOK, tplSignInOpenID)
  82. return
  83. }
  84. id, err := openid.Normalize(form.Openid)
  85. if err != nil {
  86. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form)
  87. return
  88. }
  89. form.Openid = id
  90. log.Trace("OpenID uri: " + id)
  91. err = allowedOpenIDURI(id)
  92. if err != nil {
  93. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form)
  94. return
  95. }
  96. redirectTo := setting.AppURL + "user/login/openid"
  97. url, err := openid.RedirectURL(id, redirectTo, setting.AppURL)
  98. if err != nil {
  99. log.Error("Error in OpenID redirect URL: %s, %v", redirectTo, err.Error())
  100. ctx.RenderWithErr(fmt.Sprintf("Unable to find OpenID provider in %s", redirectTo), tplSignInOpenID, &form)
  101. return
  102. }
  103. // Request optional nickname and email info
  104. // NOTE: change to `openid.sreg.required` to require it
  105. url += "&openid.ns.sreg=http%3A%2F%2Fopenid.net%2Fextensions%2Fsreg%2F1.1"
  106. url += "&openid.sreg.optional=nickname%2Cemail"
  107. log.Trace("Form-passed openid-remember: %t", form.Remember)
  108. if err := ctx.Session.Set("openid_signin_remember", form.Remember); err != nil {
  109. log.Error("SignInOpenIDPost: Could not set openid_signin_remember in session: %v", err)
  110. }
  111. if err := ctx.Session.Release(); err != nil {
  112. log.Error("SignInOpenIDPost: Unable to save changes to the session: %v", err)
  113. }
  114. ctx.Redirect(url)
  115. }
  116. // signInOpenIDVerify handles response from OpenID provider
  117. func signInOpenIDVerify(ctx *context.Context) {
  118. log.Trace("Incoming call to: %s", ctx.Req.URL.String())
  119. fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
  120. log.Trace("Full URL: %s", fullURL)
  121. id, err := openid.Verify(fullURL)
  122. if err != nil {
  123. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
  124. Openid: id,
  125. })
  126. return
  127. }
  128. log.Trace("Verified ID: %s", id)
  129. /* Now we should seek for the user and log him in, or prompt
  130. * to register if not found */
  131. u, err := user_model.GetUserByOpenID(ctx, id)
  132. if err != nil {
  133. if !user_model.IsErrUserNotExist(err) {
  134. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
  135. Openid: id,
  136. })
  137. return
  138. }
  139. log.Error("signInOpenIDVerify: %v", err)
  140. }
  141. if u != nil {
  142. log.Trace("User exists, logging in")
  143. remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
  144. log.Trace("Session stored openid-remember: %t", remember)
  145. handleSignIn(ctx, u, remember)
  146. return
  147. }
  148. log.Trace("User with openid: %s does not exist, should connect or register", id)
  149. parsedURL, err := url.Parse(fullURL)
  150. if err != nil {
  151. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
  152. Openid: id,
  153. })
  154. return
  155. }
  156. values, err := url.ParseQuery(parsedURL.RawQuery)
  157. if err != nil {
  158. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
  159. Openid: id,
  160. })
  161. return
  162. }
  163. email := values.Get("openid.sreg.email")
  164. nickname := values.Get("openid.sreg.nickname")
  165. log.Trace("User has email=%s and nickname=%s", email, nickname)
  166. if email != "" {
  167. u, err = user_model.GetUserByEmail(ctx, email)
  168. if err != nil {
  169. if !user_model.IsErrUserNotExist(err) {
  170. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
  171. Openid: id,
  172. })
  173. return
  174. }
  175. log.Error("signInOpenIDVerify: %v", err)
  176. }
  177. if u != nil {
  178. log.Trace("Local user %s has OpenID provided email %s", u.LowerName, email)
  179. }
  180. }
  181. if u == nil && nickname != "" {
  182. u, _ = user_model.GetUserByName(ctx, nickname)
  183. if err != nil {
  184. if !user_model.IsErrUserNotExist(err) {
  185. ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
  186. Openid: id,
  187. })
  188. return
  189. }
  190. }
  191. if u != nil {
  192. log.Trace("Local user %s has OpenID provided nickname %s", u.LowerName, nickname)
  193. }
  194. }
  195. if u != nil {
  196. nickname = u.LowerName
  197. }
  198. if err := updateSession(ctx, nil, map[string]any{
  199. "openid_verified_uri": id,
  200. "openid_determined_email": email,
  201. "openid_determined_username": nickname,
  202. }); err != nil {
  203. ctx.ServerError("updateSession", err)
  204. return
  205. }
  206. if u != nil || !setting.Service.EnableOpenIDSignUp || setting.Service.AllowOnlyInternalRegistration {
  207. ctx.Redirect(setting.AppSubURL + "/user/openid/connect")
  208. } else {
  209. ctx.Redirect(setting.AppSubURL + "/user/openid/register")
  210. }
  211. }
  212. // ConnectOpenID shows a form to connect an OpenID URI to an existing account
  213. func ConnectOpenID(ctx *context.Context) {
  214. oid, _ := ctx.Session.Get("openid_verified_uri").(string)
  215. if oid == "" {
  216. ctx.Redirect(setting.AppSubURL + "/user/login/openid")
  217. return
  218. }
  219. ctx.Data["Title"] = "OpenID connect"
  220. ctx.Data["PageIsSignIn"] = true
  221. ctx.Data["PageIsOpenIDConnect"] = true
  222. ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
  223. ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
  224. ctx.Data["OpenID"] = oid
  225. userName, _ := ctx.Session.Get("openid_determined_username").(string)
  226. if userName != "" {
  227. ctx.Data["user_name"] = userName
  228. }
  229. ctx.HTML(http.StatusOK, tplConnectOID)
  230. }
  231. // ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account
  232. func ConnectOpenIDPost(ctx *context.Context) {
  233. form := web.GetForm(ctx).(*forms.ConnectOpenIDForm)
  234. oid, _ := ctx.Session.Get("openid_verified_uri").(string)
  235. if oid == "" {
  236. ctx.Redirect(setting.AppSubURL + "/user/login/openid")
  237. return
  238. }
  239. ctx.Data["Title"] = "OpenID connect"
  240. ctx.Data["PageIsSignIn"] = true
  241. ctx.Data["PageIsOpenIDConnect"] = true
  242. ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
  243. ctx.Data["OpenID"] = oid
  244. u, _, err := auth.UserSignIn(ctx, form.UserName, form.Password)
  245. if err != nil {
  246. handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err)
  247. return
  248. }
  249. // add OpenID for the user
  250. userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
  251. if err = user_model.AddUserOpenID(ctx, userOID); err != nil {
  252. if user_model.IsErrOpenIDAlreadyUsed(err) {
  253. ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form)
  254. return
  255. }
  256. ctx.ServerError("AddUserOpenID", err)
  257. return
  258. }
  259. ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
  260. remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
  261. log.Trace("Session stored openid-remember: %t", remember)
  262. handleSignIn(ctx, u, remember)
  263. }
  264. // RegisterOpenID shows a form to create a new user authenticated via an OpenID URI
  265. func RegisterOpenID(ctx *context.Context) {
  266. oid, _ := ctx.Session.Get("openid_verified_uri").(string)
  267. if oid == "" {
  268. ctx.Redirect(setting.AppSubURL + "/user/login/openid")
  269. return
  270. }
  271. ctx.Data["Title"] = "OpenID signup"
  272. ctx.Data["PageIsSignIn"] = true
  273. ctx.Data["PageIsOpenIDRegister"] = true
  274. ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
  275. ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
  276. ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
  277. ctx.Data["Captcha"] = context.GetImageCaptcha()
  278. ctx.Data["CaptchaType"] = setting.Service.CaptchaType
  279. ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
  280. ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
  281. ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
  282. ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
  283. ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
  284. ctx.Data["OpenID"] = oid
  285. userName, _ := ctx.Session.Get("openid_determined_username").(string)
  286. if userName != "" {
  287. ctx.Data["user_name"] = userName
  288. }
  289. email, _ := ctx.Session.Get("openid_determined_email").(string)
  290. if email != "" {
  291. ctx.Data["email"] = email
  292. }
  293. ctx.HTML(http.StatusOK, tplSignUpOID)
  294. }
  295. // RegisterOpenIDPost handles submission of a form to create a new user authenticated via an OpenID URI
  296. func RegisterOpenIDPost(ctx *context.Context) {
  297. form := web.GetForm(ctx).(*forms.SignUpOpenIDForm)
  298. oid, _ := ctx.Session.Get("openid_verified_uri").(string)
  299. if oid == "" {
  300. ctx.Redirect(setting.AppSubURL + "/user/login/openid")
  301. return
  302. }
  303. ctx.Data["Title"] = "OpenID signup"
  304. ctx.Data["PageIsSignIn"] = true
  305. ctx.Data["PageIsOpenIDRegister"] = true
  306. ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
  307. context.SetCaptchaData(ctx)
  308. ctx.Data["OpenID"] = oid
  309. if setting.Service.AllowOnlyInternalRegistration {
  310. ctx.Error(http.StatusForbidden)
  311. return
  312. }
  313. if setting.Service.EnableCaptcha {
  314. if err := ctx.Req.ParseForm(); err != nil {
  315. ctx.ServerError("", err)
  316. return
  317. }
  318. context.VerifyCaptcha(ctx, tplSignUpOID, form)
  319. }
  320. length := setting.MinPasswordLength
  321. if length < 256 {
  322. length = 256
  323. }
  324. password, err := util.CryptoRandomString(int64(length))
  325. if err != nil {
  326. ctx.RenderWithErr(err.Error(), tplSignUpOID, form)
  327. return
  328. }
  329. u := &user_model.User{
  330. Name: form.UserName,
  331. Email: form.Email,
  332. Passwd: password,
  333. }
  334. if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false) {
  335. // error already handled
  336. return
  337. }
  338. // add OpenID for the user
  339. userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
  340. if err = user_model.AddUserOpenID(ctx, userOID); err != nil {
  341. if user_model.IsErrOpenIDAlreadyUsed(err) {
  342. ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplSignUpOID, &form)
  343. return
  344. }
  345. ctx.ServerError("AddUserOpenID", err)
  346. return
  347. }
  348. if !handleUserCreated(ctx, u, nil) {
  349. // error already handled
  350. return
  351. }
  352. remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
  353. log.Trace("Session stored openid-remember: %t", remember)
  354. handleSignIn(ctx, u, remember)
  355. }