gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "encoding/gob"
  6. "errors"
  7. "fmt"
  8. "html"
  9. "io"
  10. "net/http"
  11. "sort"
  12. "strings"
  13. "code.gitea.io/gitea/models/auth"
  14. user_model "code.gitea.io/gitea/models/user"
  15. auth_module "code.gitea.io/gitea/modules/auth"
  16. "code.gitea.io/gitea/modules/container"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/optional"
  19. "code.gitea.io/gitea/modules/session"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/web/middleware"
  22. source_service "code.gitea.io/gitea/services/auth/source"
  23. "code.gitea.io/gitea/services/auth/source/oauth2"
  24. "code.gitea.io/gitea/services/context"
  25. "code.gitea.io/gitea/services/externalaccount"
  26. user_service "code.gitea.io/gitea/services/user"
  27. "github.com/markbates/goth"
  28. "github.com/markbates/goth/gothic"
  29. go_oauth2 "golang.org/x/oauth2"
  30. )
  31. // SignInOAuth handles the OAuth2 login buttons
  32. func SignInOAuth(ctx *context.Context) {
  33. authName := ctx.PathParam("provider")
  34. authSource, err := auth.GetActiveOAuth2SourceByAuthName(ctx, authName)
  35. if err != nil {
  36. ctx.ServerError("SignIn", err)
  37. return
  38. }
  39. redirectTo := ctx.FormString("redirect_to")
  40. if len(redirectTo) > 0 {
  41. middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
  42. }
  43. // try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
  44. user, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp)
  45. if err == nil && user != nil {
  46. // we got the user without going through the whole OAuth2 authentication flow again
  47. handleOAuth2SignIn(ctx, authSource, user, gothUser)
  48. return
  49. }
  50. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  51. if strings.Contains(err.Error(), "no provider for ") {
  52. if err = oauth2.ResetOAuth2(ctx); err != nil {
  53. ctx.ServerError("SignIn", err)
  54. return
  55. }
  56. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  57. ctx.ServerError("SignIn", err)
  58. }
  59. return
  60. }
  61. ctx.ServerError("SignIn", err)
  62. }
  63. // redirect is done in oauth2.Auth
  64. }
  65. // SignInOAuthCallback handles the callback from the given provider
  66. func SignInOAuthCallback(ctx *context.Context) {
  67. if ctx.Req.FormValue("error") != "" {
  68. var errorKeyValues []string
  69. for k, vv := range ctx.Req.Form {
  70. for _, v := range vv {
  71. errorKeyValues = append(errorKeyValues, fmt.Sprintf("%s = %s", html.EscapeString(k), html.EscapeString(v)))
  72. }
  73. }
  74. sort.Strings(errorKeyValues)
  75. ctx.Flash.Error(strings.Join(errorKeyValues, "<br>"), true)
  76. }
  77. // first look if the provider is still active
  78. authName := ctx.PathParam("provider")
  79. authSource, err := auth.GetActiveOAuth2SourceByAuthName(ctx, authName)
  80. if err != nil {
  81. ctx.ServerError("SignIn", err)
  82. return
  83. }
  84. if authSource == nil {
  85. ctx.ServerError("SignIn", errors.New("no valid provider found, check configured callback url in provider"))
  86. return
  87. }
  88. u, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp)
  89. if err != nil {
  90. if user_model.IsErrUserProhibitLogin(err) {
  91. uplerr := err.(user_model.ErrUserProhibitLogin)
  92. log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
  93. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  94. ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
  95. return
  96. }
  97. if callbackErr, ok := err.(errCallback); ok {
  98. log.Info("Failed OAuth callback: (%v) %v", callbackErr.Code, callbackErr.Description)
  99. switch callbackErr.Code {
  100. case "access_denied":
  101. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.access_denied"))
  102. case "temporarily_unavailable":
  103. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.temporarily_unavailable"))
  104. default:
  105. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.general", callbackErr.Description))
  106. }
  107. ctx.Redirect(setting.AppSubURL + "/user/login")
  108. return
  109. }
  110. if err, ok := err.(*go_oauth2.RetrieveError); ok {
  111. ctx.Flash.Error("OAuth2 RetrieveError: "+err.Error(), true)
  112. ctx.Redirect(setting.AppSubURL + "/user/login")
  113. return
  114. }
  115. ctx.ServerError("UserSignIn", err)
  116. return
  117. }
  118. if u == nil {
  119. if ctx.Doer != nil {
  120. // attach user to the current signed-in user
  121. err = externalaccount.LinkAccountToUser(ctx, authSource.ID, ctx.Doer, gothUser)
  122. if err != nil {
  123. ctx.ServerError("UserLinkAccount", err)
  124. return
  125. }
  126. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  127. return
  128. } else if !setting.Service.AllowOnlyInternalRegistration && setting.OAuth2Client.EnableAutoRegistration {
  129. // create new user with details from oauth2 provider
  130. var missingFields []string
  131. if gothUser.UserID == "" {
  132. missingFields = append(missingFields, "sub")
  133. }
  134. if gothUser.Email == "" {
  135. missingFields = append(missingFields, "email")
  136. }
  137. uname, err := extractUserNameFromOAuth2(&gothUser)
  138. if err != nil {
  139. ctx.ServerError("UserSignIn", err)
  140. return
  141. }
  142. if uname == "" {
  143. switch setting.OAuth2Client.Username {
  144. case setting.OAuth2UsernameNickname:
  145. missingFields = append(missingFields, "nickname")
  146. case setting.OAuth2UsernamePreferredUsername:
  147. missingFields = append(missingFields, "preferred_username")
  148. } // else: "UserID" and "Email" have been handled above separately
  149. }
  150. if len(missingFields) > 0 {
  151. log.Error(`OAuth2 auto registration (ENABLE_AUTO_REGISTRATION) is enabled but OAuth2 provider %q doesn't return required fields: %s. `+
  152. `Suggest to: disable auto registration, or make OPENID_CONNECT_SCOPES (for OpenIDConnect) / Authentication Source Scopes (for Admin panel) to request all required fields, and the fields shouldn't be empty.`,
  153. authSource.Name, strings.Join(missingFields, ","))
  154. // The RawData is the only way to pass the missing fields to the another page at the moment, other ways all have various problems:
  155. // by session or cookie: difficult to clean or reset; by URL: could be injected with uncontrollable content; by ctx.Flash: the link_account page is a mess ...
  156. // Since the RawData is for the provider's data, so we need to use our own prefix here to avoid conflict.
  157. if gothUser.RawData == nil {
  158. gothUser.RawData = make(map[string]any)
  159. }
  160. gothUser.RawData["__giteaAutoRegMissingFields"] = missingFields
  161. showLinkingLogin(ctx, authSource.ID, gothUser)
  162. return
  163. }
  164. u = &user_model.User{
  165. Name: uname,
  166. Email: gothUser.Email,
  167. LoginType: auth.OAuth2,
  168. LoginSource: authSource.ID,
  169. LoginName: gothUser.UserID,
  170. }
  171. overwriteDefault := &user_model.CreateUserOverwriteOptions{
  172. IsActive: optional.Some(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
  173. }
  174. source := authSource.Cfg.(*oauth2.Source)
  175. isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser)
  176. u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue
  177. u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted)
  178. linkAccountData := &LinkAccountData{authSource.ID, gothUser}
  179. if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingDisabled {
  180. linkAccountData = nil
  181. }
  182. if !createAndHandleCreatedUser(ctx, "", nil, u, overwriteDefault, linkAccountData) {
  183. // error already handled
  184. return
  185. }
  186. if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
  187. ctx.ServerError("SyncGroupsToTeams", err)
  188. return
  189. }
  190. } else {
  191. // no existing user is found, request attach or new account
  192. showLinkingLogin(ctx, authSource.ID, gothUser)
  193. return
  194. }
  195. }
  196. handleOAuth2SignIn(ctx, authSource, u, gothUser)
  197. }
  198. func claimValueToStringSet(claimValue any) container.Set[string] {
  199. var groups []string
  200. switch rawGroup := claimValue.(type) {
  201. case []string:
  202. groups = rawGroup
  203. case []any:
  204. for _, group := range rawGroup {
  205. groups = append(groups, fmt.Sprintf("%s", group))
  206. }
  207. default:
  208. str := fmt.Sprintf("%s", rawGroup)
  209. groups = strings.Split(str, ",")
  210. }
  211. return container.SetOf(groups...)
  212. }
  213. func syncGroupsToTeams(ctx *context.Context, source *oauth2.Source, gothUser *goth.User, u *user_model.User) error {
  214. if source.GroupTeamMap != "" || source.GroupTeamMapRemoval {
  215. groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap)
  216. if err != nil {
  217. return err
  218. }
  219. groups := getClaimedGroups(source, gothUser)
  220. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, source.GroupTeamMapRemoval); err != nil {
  221. return err
  222. }
  223. }
  224. return nil
  225. }
  226. func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[string] {
  227. groupClaims, has := gothUser.RawData[source.GroupClaimName]
  228. if !has {
  229. return nil
  230. }
  231. return claimValueToStringSet(groupClaims)
  232. }
  233. func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin optional.Option[user_service.UpdateOptionField[bool]], isRestricted optional.Option[bool]) {
  234. groups := getClaimedGroups(source, gothUser)
  235. if source.AdminGroup != "" {
  236. isAdmin = user_service.UpdateOptionFieldFromSync(groups.Contains(source.AdminGroup))
  237. }
  238. if source.RestrictedGroup != "" {
  239. isRestricted = optional.Some(groups.Contains(source.RestrictedGroup))
  240. }
  241. return isAdmin, isRestricted
  242. }
  243. type LinkAccountData struct {
  244. AuthSourceID int64
  245. GothUser goth.User
  246. }
  247. func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
  248. gob.Register(LinkAccountData{})
  249. v, ok := ctx.Session.Get("linkAccountData").(LinkAccountData)
  250. if !ok {
  251. return nil
  252. }
  253. return &v
  254. }
  255. func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error {
  256. gob.Register(LinkAccountData{})
  257. return updateSession(ctx, nil, map[string]any{
  258. "linkAccountData": linkAccountData,
  259. })
  260. }
  261. func showLinkingLogin(ctx *context.Context, authSourceID int64, gothUser goth.User) {
  262. if err := Oauth2SetLinkAccountData(ctx, LinkAccountData{authSourceID, gothUser}); err != nil {
  263. ctx.ServerError("Oauth2SetLinkAccountData", err)
  264. return
  265. }
  266. ctx.Redirect(setting.AppSubURL + "/user/link_account")
  267. }
  268. func oauth2UpdateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
  269. if setting.OAuth2Client.UpdateAvatar && len(url) > 0 {
  270. resp, err := http.Get(url)
  271. if err == nil {
  272. defer func() {
  273. _ = resp.Body.Close()
  274. }()
  275. }
  276. // ignore any error
  277. if err == nil && resp.StatusCode == http.StatusOK {
  278. data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1))
  279. if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize {
  280. _ = user_service.UploadAvatar(ctx, u, data)
  281. }
  282. }
  283. }
  284. }
  285. func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_model.User, gothUser goth.User) {
  286. oauth2SignInSync(ctx, authSource.ID, u, gothUser)
  287. if ctx.Written() {
  288. return
  289. }
  290. needs2FA := false
  291. if !authSource.TwoFactorShouldSkip() {
  292. _, err := auth.GetTwoFactorByUID(ctx, u.ID)
  293. if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
  294. ctx.ServerError("UserSignIn", err)
  295. return
  296. }
  297. needs2FA = err == nil
  298. }
  299. oauth2Source := authSource.Cfg.(*oauth2.Source)
  300. groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap)
  301. if err != nil {
  302. ctx.ServerError("UnmarshalGroupTeamMapping", err)
  303. return
  304. }
  305. groups := getClaimedGroups(oauth2Source, &gothUser)
  306. opts := &user_service.UpdateOptions{}
  307. // Reactivate user if they are deactivated
  308. if !u.IsActive {
  309. opts.IsActive = optional.Some(true)
  310. }
  311. // Update GroupClaims
  312. opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
  313. if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
  314. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
  315. ctx.ServerError("SyncGroupsToTeams", err)
  316. return
  317. }
  318. }
  319. if err := externalaccount.EnsureLinkExternalToUser(ctx, authSource.ID, u, gothUser); err != nil {
  320. ctx.ServerError("EnsureLinkExternalToUser", err)
  321. return
  322. }
  323. // If this user is enrolled in 2FA and this source doesn't override it,
  324. // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
  325. if !needs2FA {
  326. // Register last login
  327. opts.SetLastLogin = true
  328. if err := user_service.UpdateUser(ctx, u, opts); err != nil {
  329. ctx.ServerError("UpdateUser", err)
  330. return
  331. }
  332. userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
  333. if err != nil {
  334. ctx.ServerError("UpdateUser", err)
  335. return
  336. }
  337. if err := updateSession(ctx, nil, map[string]any{
  338. session.KeyUID: u.ID,
  339. session.KeyUname: u.Name,
  340. session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
  341. }); err != nil {
  342. ctx.ServerError("updateSession", err)
  343. return
  344. }
  345. // force to generate a new CSRF token
  346. ctx.Csrf.PrepareForSessionUser(ctx)
  347. if err := resetLocale(ctx, u); err != nil {
  348. ctx.ServerError("resetLocale", err)
  349. return
  350. }
  351. if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
  352. middleware.DeleteRedirectToCookie(ctx.Resp)
  353. ctx.RedirectToCurrentSite(redirectTo)
  354. return
  355. }
  356. ctx.Redirect(setting.AppSubURL + "/")
  357. return
  358. }
  359. if opts.IsActive.Has() || opts.IsAdmin.Has() || opts.IsRestricted.Has() {
  360. if err := user_service.UpdateUser(ctx, u, opts); err != nil {
  361. ctx.ServerError("UpdateUser", err)
  362. return
  363. }
  364. }
  365. if err := updateSession(ctx, nil, map[string]any{
  366. // User needs to use 2FA, save data and redirect to 2FA page.
  367. "twofaUid": u.ID,
  368. "twofaRemember": false,
  369. }); err != nil {
  370. ctx.ServerError("updateSession", err)
  371. return
  372. }
  373. // If WebAuthn is enrolled -> Redirect to WebAuthn instead
  374. regs, err := auth.GetWebAuthnCredentialsByUID(ctx, u.ID)
  375. if err == nil && len(regs) > 0 {
  376. ctx.Redirect(setting.AppSubURL + "/user/webauthn")
  377. return
  378. }
  379. ctx.Redirect(setting.AppSubURL + "/user/two_factor")
  380. }
  381. // OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
  382. // login the user
  383. func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
  384. oauth2Source := authSource.Cfg.(*oauth2.Source)
  385. // Make sure that the response is not an error response.
  386. errorName := request.FormValue("error")
  387. if len(errorName) > 0 {
  388. errorDescription := request.FormValue("error_description")
  389. // Delete the goth session
  390. err := gothic.Logout(response, request)
  391. if err != nil {
  392. return nil, goth.User{}, err
  393. }
  394. return nil, goth.User{}, errCallback{
  395. Code: errorName,
  396. Description: errorDescription,
  397. }
  398. }
  399. // Proceed to authenticate through goth.
  400. gothUser, err := oauth2Source.Callback(request, response)
  401. if err != nil {
  402. if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
  403. err = fmt.Errorf("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength)
  404. log.Error("oauth2Source.Callback failed: %v", err)
  405. } else {
  406. err = errCallback{Code: "internal", Description: err.Error()}
  407. }
  408. return nil, goth.User{}, err
  409. }
  410. if oauth2Source.RequiredClaimName != "" {
  411. claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
  412. if !has {
  413. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  414. }
  415. if oauth2Source.RequiredClaimValue != "" {
  416. groups := claimValueToStringSet(claimInterface)
  417. if !groups.Contains(oauth2Source.RequiredClaimValue) {
  418. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  419. }
  420. }
  421. }
  422. user := &user_model.User{
  423. LoginName: gothUser.UserID,
  424. LoginType: auth.OAuth2,
  425. LoginSource: authSource.ID,
  426. }
  427. hasUser, err := user_model.GetUser(ctx, user)
  428. if err != nil {
  429. return nil, goth.User{}, err
  430. }
  431. if hasUser {
  432. return user, gothUser, nil
  433. }
  434. // search in external linked users
  435. externalLoginUser := &user_model.ExternalLoginUser{
  436. ExternalID: gothUser.UserID,
  437. LoginSourceID: authSource.ID,
  438. }
  439. hasUser, err = user_model.GetExternalLogin(request.Context(), externalLoginUser)
  440. if err != nil {
  441. return nil, goth.User{}, err
  442. }
  443. if hasUser {
  444. user, err = user_model.GetUserByID(request.Context(), externalLoginUser.UserID)
  445. return user, gothUser, err
  446. }
  447. // no user found to login
  448. return nil, gothUser, nil
  449. }