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.

oauth.go 41KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. go_context "context"
  6. "encoding/base64"
  7. "errors"
  8. "fmt"
  9. "html"
  10. "html/template"
  11. "io"
  12. "net/http"
  13. "net/url"
  14. "sort"
  15. "strings"
  16. "code.gitea.io/gitea/models/auth"
  17. org_model "code.gitea.io/gitea/models/organization"
  18. user_model "code.gitea.io/gitea/models/user"
  19. auth_module "code.gitea.io/gitea/modules/auth"
  20. "code.gitea.io/gitea/modules/base"
  21. "code.gitea.io/gitea/modules/container"
  22. "code.gitea.io/gitea/modules/context"
  23. "code.gitea.io/gitea/modules/json"
  24. "code.gitea.io/gitea/modules/log"
  25. "code.gitea.io/gitea/modules/optional"
  26. "code.gitea.io/gitea/modules/setting"
  27. "code.gitea.io/gitea/modules/timeutil"
  28. "code.gitea.io/gitea/modules/util"
  29. "code.gitea.io/gitea/modules/web"
  30. "code.gitea.io/gitea/modules/web/middleware"
  31. auth_service "code.gitea.io/gitea/services/auth"
  32. source_service "code.gitea.io/gitea/services/auth/source"
  33. "code.gitea.io/gitea/services/auth/source/oauth2"
  34. "code.gitea.io/gitea/services/externalaccount"
  35. "code.gitea.io/gitea/services/forms"
  36. user_service "code.gitea.io/gitea/services/user"
  37. "gitea.com/go-chi/binding"
  38. "github.com/golang-jwt/jwt/v5"
  39. "github.com/markbates/goth"
  40. "github.com/markbates/goth/gothic"
  41. go_oauth2 "golang.org/x/oauth2"
  42. )
  43. const (
  44. tplGrantAccess base.TplName = "user/auth/grant"
  45. tplGrantError base.TplName = "user/auth/grant_error"
  46. )
  47. // TODO move error and responses to SDK or models
  48. // AuthorizeErrorCode represents an error code specified in RFC 6749
  49. // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
  50. type AuthorizeErrorCode string
  51. const (
  52. // ErrorCodeInvalidRequest represents the according error in RFC 6749
  53. ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request"
  54. // ErrorCodeUnauthorizedClient represents the according error in RFC 6749
  55. ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client"
  56. // ErrorCodeAccessDenied represents the according error in RFC 6749
  57. ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied"
  58. // ErrorCodeUnsupportedResponseType represents the according error in RFC 6749
  59. ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type"
  60. // ErrorCodeInvalidScope represents the according error in RFC 6749
  61. ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope"
  62. // ErrorCodeServerError represents the according error in RFC 6749
  63. ErrorCodeServerError AuthorizeErrorCode = "server_error"
  64. // ErrorCodeTemporaryUnavailable represents the according error in RFC 6749
  65. ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable"
  66. )
  67. // AuthorizeError represents an error type specified in RFC 6749
  68. // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
  69. type AuthorizeError struct {
  70. ErrorCode AuthorizeErrorCode `json:"error" form:"error"`
  71. ErrorDescription string
  72. State string
  73. }
  74. // Error returns the error message
  75. func (err AuthorizeError) Error() string {
  76. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  77. }
  78. // AccessTokenErrorCode represents an error code specified in RFC 6749
  79. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  80. type AccessTokenErrorCode string
  81. const (
  82. // AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749
  83. AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request"
  84. // AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749
  85. AccessTokenErrorCodeInvalidClient = "invalid_client"
  86. // AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749
  87. AccessTokenErrorCodeInvalidGrant = "invalid_grant"
  88. // AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749
  89. AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client"
  90. // AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749
  91. AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type"
  92. // AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749
  93. AccessTokenErrorCodeInvalidScope = "invalid_scope"
  94. )
  95. // AccessTokenError represents an error response specified in RFC 6749
  96. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  97. type AccessTokenError struct {
  98. ErrorCode AccessTokenErrorCode `json:"error" form:"error"`
  99. ErrorDescription string `json:"error_description"`
  100. }
  101. // Error returns the error message
  102. func (err AccessTokenError) Error() string {
  103. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  104. }
  105. // errCallback represents a oauth2 callback error
  106. type errCallback struct {
  107. Code string
  108. Description string
  109. }
  110. func (err errCallback) Error() string {
  111. return err.Description
  112. }
  113. // TokenType specifies the kind of token
  114. type TokenType string
  115. const (
  116. // TokenTypeBearer represents a token type specified in RFC 6749
  117. TokenTypeBearer TokenType = "bearer"
  118. // TokenTypeMAC represents a token type specified in RFC 6749
  119. TokenTypeMAC = "mac"
  120. )
  121. // AccessTokenResponse represents a successful access token response
  122. // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2
  123. type AccessTokenResponse struct {
  124. AccessToken string `json:"access_token"`
  125. TokenType TokenType `json:"token_type"`
  126. ExpiresIn int64 `json:"expires_in"`
  127. RefreshToken string `json:"refresh_token"`
  128. IDToken string `json:"id_token,omitempty"`
  129. }
  130. func newAccessTokenResponse(ctx go_context.Context, grant *auth.OAuth2Grant, serverKey, clientKey oauth2.JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
  131. if setting.OAuth2.InvalidateRefreshTokens {
  132. if err := grant.IncreaseCounter(ctx); err != nil {
  133. return nil, &AccessTokenError{
  134. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  135. ErrorDescription: "cannot increase the grant counter",
  136. }
  137. }
  138. }
  139. // generate access token to access the API
  140. expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
  141. accessToken := &oauth2.Token{
  142. GrantID: grant.ID,
  143. Type: oauth2.TypeAccessToken,
  144. RegisteredClaims: jwt.RegisteredClaims{
  145. ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()),
  146. },
  147. }
  148. signedAccessToken, err := accessToken.SignToken(serverKey)
  149. if err != nil {
  150. return nil, &AccessTokenError{
  151. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  152. ErrorDescription: "cannot sign token",
  153. }
  154. }
  155. // generate refresh token to request an access token after it expired later
  156. refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime()
  157. refreshToken := &oauth2.Token{
  158. GrantID: grant.ID,
  159. Counter: grant.Counter,
  160. Type: oauth2.TypeRefreshToken,
  161. RegisteredClaims: jwt.RegisteredClaims{
  162. ExpiresAt: jwt.NewNumericDate(refreshExpirationDate),
  163. },
  164. }
  165. signedRefreshToken, err := refreshToken.SignToken(serverKey)
  166. if err != nil {
  167. return nil, &AccessTokenError{
  168. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  169. ErrorDescription: "cannot sign token",
  170. }
  171. }
  172. // generate OpenID Connect id_token
  173. signedIDToken := ""
  174. if grant.ScopeContains("openid") {
  175. app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID)
  176. if err != nil {
  177. return nil, &AccessTokenError{
  178. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  179. ErrorDescription: "cannot find application",
  180. }
  181. }
  182. user, err := user_model.GetUserByID(ctx, grant.UserID)
  183. if err != nil {
  184. if user_model.IsErrUserNotExist(err) {
  185. return nil, &AccessTokenError{
  186. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  187. ErrorDescription: "cannot find user",
  188. }
  189. }
  190. log.Error("Error loading user: %v", err)
  191. return nil, &AccessTokenError{
  192. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  193. ErrorDescription: "server error",
  194. }
  195. }
  196. idToken := &oauth2.OIDCToken{
  197. RegisteredClaims: jwt.RegisteredClaims{
  198. ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()),
  199. Issuer: setting.AppURL,
  200. Audience: []string{app.ClientID},
  201. Subject: fmt.Sprint(grant.UserID),
  202. },
  203. Nonce: grant.Nonce,
  204. }
  205. if grant.ScopeContains("profile") {
  206. idToken.Name = user.GetDisplayName()
  207. idToken.PreferredUsername = user.Name
  208. idToken.Profile = user.HTMLURL()
  209. idToken.Picture = user.AvatarLink(ctx)
  210. idToken.Website = user.Website
  211. idToken.Locale = user.Language
  212. idToken.UpdatedAt = user.UpdatedUnix
  213. }
  214. if grant.ScopeContains("email") {
  215. idToken.Email = user.Email
  216. idToken.EmailVerified = user.IsActive
  217. }
  218. if grant.ScopeContains("groups") {
  219. groups, err := getOAuthGroupsForUser(ctx, user)
  220. if err != nil {
  221. log.Error("Error getting groups: %v", err)
  222. return nil, &AccessTokenError{
  223. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  224. ErrorDescription: "server error",
  225. }
  226. }
  227. idToken.Groups = groups
  228. }
  229. signedIDToken, err = idToken.SignToken(clientKey)
  230. if err != nil {
  231. return nil, &AccessTokenError{
  232. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  233. ErrorDescription: "cannot sign token",
  234. }
  235. }
  236. }
  237. return &AccessTokenResponse{
  238. AccessToken: signedAccessToken,
  239. TokenType: TokenTypeBearer,
  240. ExpiresIn: setting.OAuth2.AccessTokenExpirationTime,
  241. RefreshToken: signedRefreshToken,
  242. IDToken: signedIDToken,
  243. }, nil
  244. }
  245. type userInfoResponse struct {
  246. Sub string `json:"sub"`
  247. Name string `json:"name"`
  248. Username string `json:"preferred_username"`
  249. Email string `json:"email"`
  250. Picture string `json:"picture"`
  251. Groups []string `json:"groups"`
  252. }
  253. // InfoOAuth manages request for userinfo endpoint
  254. func InfoOAuth(ctx *context.Context) {
  255. if ctx.Doer == nil || ctx.Data["AuthedMethod"] != (&auth_service.OAuth2{}).Name() {
  256. ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
  257. ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
  258. return
  259. }
  260. response := &userInfoResponse{
  261. Sub: fmt.Sprint(ctx.Doer.ID),
  262. Name: ctx.Doer.FullName,
  263. Username: ctx.Doer.Name,
  264. Email: ctx.Doer.Email,
  265. Picture: ctx.Doer.AvatarLink(ctx),
  266. }
  267. groups, err := getOAuthGroupsForUser(ctx, ctx.Doer)
  268. if err != nil {
  269. ctx.ServerError("Oauth groups for user", err)
  270. return
  271. }
  272. response.Groups = groups
  273. ctx.JSON(http.StatusOK, response)
  274. }
  275. // returns a list of "org" and "org:team" strings,
  276. // that the given user is a part of.
  277. func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User) ([]string, error) {
  278. orgs, err := org_model.GetUserOrgsList(ctx, user)
  279. if err != nil {
  280. return nil, fmt.Errorf("GetUserOrgList: %w", err)
  281. }
  282. var groups []string
  283. for _, org := range orgs {
  284. groups = append(groups, org.Name)
  285. teams, err := org.LoadTeams(ctx)
  286. if err != nil {
  287. return nil, fmt.Errorf("LoadTeams: %w", err)
  288. }
  289. for _, team := range teams {
  290. if team.IsMember(ctx, user.ID) {
  291. groups = append(groups, org.Name+":"+team.LowerName)
  292. }
  293. }
  294. }
  295. return groups, nil
  296. }
  297. // IntrospectOAuth introspects an oauth token
  298. func IntrospectOAuth(ctx *context.Context) {
  299. if ctx.Doer == nil {
  300. ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
  301. ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
  302. return
  303. }
  304. var response struct {
  305. Active bool `json:"active"`
  306. Scope string `json:"scope,omitempty"`
  307. jwt.RegisteredClaims
  308. }
  309. form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
  310. token, err := oauth2.ParseToken(form.Token, oauth2.DefaultSigningKey)
  311. if err == nil {
  312. grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID)
  313. if err == nil && grant != nil {
  314. app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID)
  315. if err == nil && app != nil {
  316. response.Active = true
  317. response.Scope = grant.Scope
  318. response.Issuer = setting.AppURL
  319. response.Audience = []string{app.ClientID}
  320. response.Subject = fmt.Sprint(grant.UserID)
  321. }
  322. }
  323. }
  324. ctx.JSON(http.StatusOK, response)
  325. }
  326. // AuthorizeOAuth manages authorize requests
  327. func AuthorizeOAuth(ctx *context.Context) {
  328. form := web.GetForm(ctx).(*forms.AuthorizationForm)
  329. errs := binding.Errors{}
  330. errs = form.Validate(ctx.Req, errs)
  331. if len(errs) > 0 {
  332. errstring := ""
  333. for _, e := range errs {
  334. errstring += e.Error() + "\n"
  335. }
  336. ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
  337. return
  338. }
  339. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  340. if err != nil {
  341. if auth.IsErrOauthClientIDInvalid(err) {
  342. handleAuthorizeError(ctx, AuthorizeError{
  343. ErrorCode: ErrorCodeUnauthorizedClient,
  344. ErrorDescription: "Client ID not registered",
  345. State: form.State,
  346. }, "")
  347. return
  348. }
  349. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  350. return
  351. }
  352. var user *user_model.User
  353. if app.UID != 0 {
  354. user, err = user_model.GetUserByID(ctx, app.UID)
  355. if err != nil {
  356. ctx.ServerError("GetUserByID", err)
  357. return
  358. }
  359. }
  360. if !app.ContainsRedirectURI(form.RedirectURI) {
  361. handleAuthorizeError(ctx, AuthorizeError{
  362. ErrorCode: ErrorCodeInvalidRequest,
  363. ErrorDescription: "Unregistered Redirect URI",
  364. State: form.State,
  365. }, "")
  366. return
  367. }
  368. if form.ResponseType != "code" {
  369. handleAuthorizeError(ctx, AuthorizeError{
  370. ErrorCode: ErrorCodeUnsupportedResponseType,
  371. ErrorDescription: "Only code response type is supported.",
  372. State: form.State,
  373. }, form.RedirectURI)
  374. return
  375. }
  376. // pkce support
  377. switch form.CodeChallengeMethod {
  378. case "S256":
  379. case "plain":
  380. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
  381. handleAuthorizeError(ctx, AuthorizeError{
  382. ErrorCode: ErrorCodeServerError,
  383. ErrorDescription: "cannot set code challenge method",
  384. State: form.State,
  385. }, form.RedirectURI)
  386. return
  387. }
  388. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
  389. handleAuthorizeError(ctx, AuthorizeError{
  390. ErrorCode: ErrorCodeServerError,
  391. ErrorDescription: "cannot set code challenge",
  392. State: form.State,
  393. }, form.RedirectURI)
  394. return
  395. }
  396. // Here we're just going to try to release the session early
  397. if err := ctx.Session.Release(); err != nil {
  398. // we'll tolerate errors here as they *should* get saved elsewhere
  399. log.Error("Unable to save changes to the session: %v", err)
  400. }
  401. case "":
  402. // "Authorization servers SHOULD reject authorization requests from native apps that don't use PKCE by returning an error message"
  403. // https://datatracker.ietf.org/doc/html/rfc8252#section-8.1
  404. if !app.ConfidentialClient {
  405. // "the authorization endpoint MUST return the authorization error response with the "error" value set to "invalid_request""
  406. // https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1
  407. handleAuthorizeError(ctx, AuthorizeError{
  408. ErrorCode: ErrorCodeInvalidRequest,
  409. ErrorDescription: "PKCE is required for public clients",
  410. State: form.State,
  411. }, form.RedirectURI)
  412. return
  413. }
  414. default:
  415. // "If the server supporting PKCE does not support the requested transformation, the authorization endpoint MUST return the authorization error response with "error" value set to "invalid_request"."
  416. // https://www.rfc-editor.org/rfc/rfc7636#section-4.4.1
  417. handleAuthorizeError(ctx, AuthorizeError{
  418. ErrorCode: ErrorCodeInvalidRequest,
  419. ErrorDescription: "unsupported code challenge method",
  420. State: form.State,
  421. }, form.RedirectURI)
  422. return
  423. }
  424. grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID)
  425. if err != nil {
  426. handleServerError(ctx, form.State, form.RedirectURI)
  427. return
  428. }
  429. // Redirect if user already granted access
  430. if grant != nil {
  431. code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
  432. if err != nil {
  433. handleServerError(ctx, form.State, form.RedirectURI)
  434. return
  435. }
  436. redirect, err := code.GenerateRedirectURI(form.State)
  437. if err != nil {
  438. handleServerError(ctx, form.State, form.RedirectURI)
  439. return
  440. }
  441. // Update nonce to reflect the new session
  442. if len(form.Nonce) > 0 {
  443. err := grant.SetNonce(ctx, form.Nonce)
  444. if err != nil {
  445. log.Error("Unable to update nonce: %v", err)
  446. }
  447. }
  448. ctx.Redirect(redirect.String())
  449. return
  450. }
  451. // show authorize page to grant access
  452. ctx.Data["Application"] = app
  453. ctx.Data["RedirectURI"] = form.RedirectURI
  454. ctx.Data["State"] = form.State
  455. ctx.Data["Scope"] = form.Scope
  456. ctx.Data["Nonce"] = form.Nonce
  457. if user != nil {
  458. ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">@%s</a>`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name)))
  459. } else {
  460. ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName)))
  461. }
  462. ctx.Data["ApplicationRedirectDomainHTML"] = template.HTML("<strong>" + html.EscapeString(form.RedirectURI) + "</strong>")
  463. // TODO document SESSION <=> FORM
  464. err = ctx.Session.Set("client_id", app.ClientID)
  465. if err != nil {
  466. handleServerError(ctx, form.State, form.RedirectURI)
  467. log.Error(err.Error())
  468. return
  469. }
  470. err = ctx.Session.Set("redirect_uri", form.RedirectURI)
  471. if err != nil {
  472. handleServerError(ctx, form.State, form.RedirectURI)
  473. log.Error(err.Error())
  474. return
  475. }
  476. err = ctx.Session.Set("state", form.State)
  477. if err != nil {
  478. handleServerError(ctx, form.State, form.RedirectURI)
  479. log.Error(err.Error())
  480. return
  481. }
  482. // Here we're just going to try to release the session early
  483. if err := ctx.Session.Release(); err != nil {
  484. // we'll tolerate errors here as they *should* get saved elsewhere
  485. log.Error("Unable to save changes to the session: %v", err)
  486. }
  487. ctx.HTML(http.StatusOK, tplGrantAccess)
  488. }
  489. // GrantApplicationOAuth manages the post request submitted when a user grants access to an application
  490. func GrantApplicationOAuth(ctx *context.Context) {
  491. form := web.GetForm(ctx).(*forms.GrantApplicationForm)
  492. if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
  493. ctx.Session.Get("redirect_uri") != form.RedirectURI {
  494. ctx.Error(http.StatusBadRequest)
  495. return
  496. }
  497. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  498. if err != nil {
  499. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  500. return
  501. }
  502. grant, err := app.CreateGrant(ctx, ctx.Doer.ID, form.Scope)
  503. if err != nil {
  504. handleAuthorizeError(ctx, AuthorizeError{
  505. State: form.State,
  506. ErrorDescription: "cannot create grant for user",
  507. ErrorCode: ErrorCodeServerError,
  508. }, form.RedirectURI)
  509. return
  510. }
  511. if len(form.Nonce) > 0 {
  512. err := grant.SetNonce(ctx, form.Nonce)
  513. if err != nil {
  514. log.Error("Unable to update nonce: %v", err)
  515. }
  516. }
  517. var codeChallenge, codeChallengeMethod string
  518. codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
  519. codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
  520. code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, codeChallenge, codeChallengeMethod)
  521. if err != nil {
  522. handleServerError(ctx, form.State, form.RedirectURI)
  523. return
  524. }
  525. redirect, err := code.GenerateRedirectURI(form.State)
  526. if err != nil {
  527. handleServerError(ctx, form.State, form.RedirectURI)
  528. return
  529. }
  530. ctx.Redirect(redirect.String(), http.StatusSeeOther)
  531. }
  532. // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
  533. func OIDCWellKnown(ctx *context.Context) {
  534. ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
  535. ctx.JSONTemplate("user/auth/oidc_wellknown")
  536. }
  537. // OIDCKeys generates the JSON Web Key Set
  538. func OIDCKeys(ctx *context.Context) {
  539. jwk, err := oauth2.DefaultSigningKey.ToJWK()
  540. if err != nil {
  541. log.Error("Error converting signing key to JWK: %v", err)
  542. ctx.Error(http.StatusInternalServerError)
  543. return
  544. }
  545. jwk["use"] = "sig"
  546. jwks := map[string][]map[string]string{
  547. "keys": {
  548. jwk,
  549. },
  550. }
  551. ctx.Resp.Header().Set("Content-Type", "application/json")
  552. enc := json.NewEncoder(ctx.Resp)
  553. if err := enc.Encode(jwks); err != nil {
  554. log.Error("Failed to encode representation as json. Error: %v", err)
  555. }
  556. }
  557. // AccessTokenOAuth manages all access token requests by the client
  558. func AccessTokenOAuth(ctx *context.Context) {
  559. form := *web.GetForm(ctx).(*forms.AccessTokenForm)
  560. // if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header
  561. if form.ClientID == "" || form.ClientSecret == "" {
  562. authHeader := ctx.Req.Header.Get("Authorization")
  563. authContent := strings.SplitN(authHeader, " ", 2)
  564. if len(authContent) == 2 && authContent[0] == "Basic" {
  565. payload, err := base64.StdEncoding.DecodeString(authContent[1])
  566. if err != nil {
  567. handleAccessTokenError(ctx, AccessTokenError{
  568. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  569. ErrorDescription: "cannot parse basic auth header",
  570. })
  571. return
  572. }
  573. pair := strings.SplitN(string(payload), ":", 2)
  574. if len(pair) != 2 {
  575. handleAccessTokenError(ctx, AccessTokenError{
  576. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  577. ErrorDescription: "cannot parse basic auth header",
  578. })
  579. return
  580. }
  581. if form.ClientID != "" && form.ClientID != pair[0] {
  582. handleAccessTokenError(ctx, AccessTokenError{
  583. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  584. ErrorDescription: "client_id in request body inconsistent with Authorization header",
  585. })
  586. return
  587. }
  588. form.ClientID = pair[0]
  589. if form.ClientSecret != "" && form.ClientSecret != pair[1] {
  590. handleAccessTokenError(ctx, AccessTokenError{
  591. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  592. ErrorDescription: "client_secret in request body inconsistent with Authorization header",
  593. })
  594. return
  595. }
  596. form.ClientSecret = pair[1]
  597. }
  598. }
  599. serverKey := oauth2.DefaultSigningKey
  600. clientKey := serverKey
  601. if serverKey.IsSymmetric() {
  602. var err error
  603. clientKey, err = oauth2.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret))
  604. if err != nil {
  605. handleAccessTokenError(ctx, AccessTokenError{
  606. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  607. ErrorDescription: "Error creating signing key",
  608. })
  609. return
  610. }
  611. }
  612. switch form.GrantType {
  613. case "refresh_token":
  614. handleRefreshToken(ctx, form, serverKey, clientKey)
  615. case "authorization_code":
  616. handleAuthorizationCode(ctx, form, serverKey, clientKey)
  617. default:
  618. handleAccessTokenError(ctx, AccessTokenError{
  619. ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
  620. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  621. })
  622. }
  623. }
  624. func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
  625. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  626. if err != nil {
  627. handleAccessTokenError(ctx, AccessTokenError{
  628. ErrorCode: AccessTokenErrorCodeInvalidClient,
  629. ErrorDescription: fmt.Sprintf("cannot load client with client id: %q", form.ClientID),
  630. })
  631. return
  632. }
  633. // "The authorization server MUST ... require client authentication for confidential clients"
  634. // https://datatracker.ietf.org/doc/html/rfc6749#section-6
  635. if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  636. errorDescription := "invalid client secret"
  637. if form.ClientSecret == "" {
  638. errorDescription = "invalid empty client secret"
  639. }
  640. // "invalid_client ... Client authentication failed"
  641. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  642. handleAccessTokenError(ctx, AccessTokenError{
  643. ErrorCode: AccessTokenErrorCodeInvalidClient,
  644. ErrorDescription: errorDescription,
  645. })
  646. return
  647. }
  648. token, err := oauth2.ParseToken(form.RefreshToken, serverKey)
  649. if err != nil {
  650. handleAccessTokenError(ctx, AccessTokenError{
  651. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  652. ErrorDescription: "unable to parse refresh token",
  653. })
  654. return
  655. }
  656. // get grant before increasing counter
  657. grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID)
  658. if err != nil || grant == nil {
  659. handleAccessTokenError(ctx, AccessTokenError{
  660. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  661. ErrorDescription: "grant does not exist",
  662. })
  663. return
  664. }
  665. // check if token got already used
  666. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  667. handleAccessTokenError(ctx, AccessTokenError{
  668. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  669. ErrorDescription: "token was already used",
  670. })
  671. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  672. return
  673. }
  674. accessToken, tokenErr := newAccessTokenResponse(ctx, grant, serverKey, clientKey)
  675. if tokenErr != nil {
  676. handleAccessTokenError(ctx, *tokenErr)
  677. return
  678. }
  679. ctx.JSON(http.StatusOK, accessToken)
  680. }
  681. func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
  682. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  683. if err != nil {
  684. handleAccessTokenError(ctx, AccessTokenError{
  685. ErrorCode: AccessTokenErrorCodeInvalidClient,
  686. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  687. })
  688. return
  689. }
  690. if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  691. errorDescription := "invalid client secret"
  692. if form.ClientSecret == "" {
  693. errorDescription = "invalid empty client secret"
  694. }
  695. handleAccessTokenError(ctx, AccessTokenError{
  696. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  697. ErrorDescription: errorDescription,
  698. })
  699. return
  700. }
  701. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  702. handleAccessTokenError(ctx, AccessTokenError{
  703. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  704. ErrorDescription: "unexpected redirect URI",
  705. })
  706. return
  707. }
  708. authorizationCode, err := auth.GetOAuth2AuthorizationByCode(ctx, form.Code)
  709. if err != nil || authorizationCode == nil {
  710. handleAccessTokenError(ctx, AccessTokenError{
  711. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  712. ErrorDescription: "client is not authorized",
  713. })
  714. return
  715. }
  716. // check if code verifier authorizes the client, PKCE support
  717. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  718. handleAccessTokenError(ctx, AccessTokenError{
  719. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  720. ErrorDescription: "failed PKCE code challenge",
  721. })
  722. return
  723. }
  724. // check if granted for this application
  725. if authorizationCode.Grant.ApplicationID != app.ID {
  726. handleAccessTokenError(ctx, AccessTokenError{
  727. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  728. ErrorDescription: "invalid grant",
  729. })
  730. return
  731. }
  732. // remove token from database to deny duplicate usage
  733. if err := authorizationCode.Invalidate(ctx); err != nil {
  734. handleAccessTokenError(ctx, AccessTokenError{
  735. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  736. ErrorDescription: "cannot proceed your request",
  737. })
  738. }
  739. resp, tokenErr := newAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey)
  740. if tokenErr != nil {
  741. handleAccessTokenError(ctx, *tokenErr)
  742. return
  743. }
  744. // send successful response
  745. ctx.JSON(http.StatusOK, resp)
  746. }
  747. func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
  748. ctx.JSON(http.StatusBadRequest, acErr)
  749. }
  750. func handleServerError(ctx *context.Context, state, redirectURI string) {
  751. handleAuthorizeError(ctx, AuthorizeError{
  752. ErrorCode: ErrorCodeServerError,
  753. ErrorDescription: "A server error occurred",
  754. State: state,
  755. }, redirectURI)
  756. }
  757. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  758. if redirectURI == "" {
  759. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  760. ctx.Data["Error"] = authErr
  761. ctx.HTML(http.StatusBadRequest, tplGrantError)
  762. return
  763. }
  764. redirect, err := url.Parse(redirectURI)
  765. if err != nil {
  766. ctx.ServerError("url.Parse", err)
  767. return
  768. }
  769. q := redirect.Query()
  770. q.Set("error", string(authErr.ErrorCode))
  771. q.Set("error_description", authErr.ErrorDescription)
  772. q.Set("state", authErr.State)
  773. redirect.RawQuery = q.Encode()
  774. ctx.Redirect(redirect.String(), http.StatusSeeOther)
  775. }
  776. // SignInOAuth handles the OAuth2 login buttons
  777. func SignInOAuth(ctx *context.Context) {
  778. provider := ctx.Params(":provider")
  779. authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
  780. if err != nil {
  781. ctx.ServerError("SignIn", err)
  782. return
  783. }
  784. redirectTo := ctx.FormString("redirect_to")
  785. if len(redirectTo) > 0 {
  786. middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
  787. }
  788. // try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
  789. user, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp)
  790. if err == nil && user != nil {
  791. // we got the user without going through the whole OAuth2 authentication flow again
  792. handleOAuth2SignIn(ctx, authSource, user, gothUser)
  793. return
  794. }
  795. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  796. if strings.Contains(err.Error(), "no provider for ") {
  797. if err = oauth2.ResetOAuth2(ctx); err != nil {
  798. ctx.ServerError("SignIn", err)
  799. return
  800. }
  801. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  802. ctx.ServerError("SignIn", err)
  803. }
  804. return
  805. }
  806. ctx.ServerError("SignIn", err)
  807. }
  808. // redirect is done in oauth2.Auth
  809. }
  810. // SignInOAuthCallback handles the callback from the given provider
  811. func SignInOAuthCallback(ctx *context.Context) {
  812. provider := ctx.Params(":provider")
  813. if ctx.Req.FormValue("error") != "" {
  814. var errorKeyValues []string
  815. for k, vv := range ctx.Req.Form {
  816. for _, v := range vv {
  817. errorKeyValues = append(errorKeyValues, fmt.Sprintf("%s = %s", html.EscapeString(k), html.EscapeString(v)))
  818. }
  819. }
  820. sort.Strings(errorKeyValues)
  821. ctx.Flash.Error(strings.Join(errorKeyValues, "<br>"), true)
  822. }
  823. // first look if the provider is still active
  824. authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
  825. if err != nil {
  826. ctx.ServerError("SignIn", err)
  827. return
  828. }
  829. if authSource == nil {
  830. ctx.ServerError("SignIn", errors.New("no valid provider found, check configured callback url in provider"))
  831. return
  832. }
  833. u, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp)
  834. if err != nil {
  835. if user_model.IsErrUserProhibitLogin(err) {
  836. uplerr := err.(user_model.ErrUserProhibitLogin)
  837. log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
  838. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  839. ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
  840. return
  841. }
  842. if callbackErr, ok := err.(errCallback); ok {
  843. log.Info("Failed OAuth callback: (%v) %v", callbackErr.Code, callbackErr.Description)
  844. switch callbackErr.Code {
  845. case "access_denied":
  846. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.access_denied"))
  847. case "temporarily_unavailable":
  848. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.temporarily_unavailable"))
  849. default:
  850. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error"))
  851. }
  852. ctx.Redirect(setting.AppSubURL + "/user/login")
  853. return
  854. }
  855. if err, ok := err.(*go_oauth2.RetrieveError); ok {
  856. ctx.Flash.Error("OAuth2 RetrieveError: "+err.Error(), true)
  857. }
  858. ctx.ServerError("UserSignIn", err)
  859. return
  860. }
  861. if u == nil {
  862. if ctx.Doer != nil {
  863. // attach user to already logged in user
  864. err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
  865. if err != nil {
  866. ctx.ServerError("UserLinkAccount", err)
  867. return
  868. }
  869. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  870. return
  871. } else if !setting.Service.AllowOnlyInternalRegistration && setting.OAuth2Client.EnableAutoRegistration {
  872. // create new user with details from oauth2 provider
  873. var missingFields []string
  874. if gothUser.UserID == "" {
  875. missingFields = append(missingFields, "sub")
  876. }
  877. if gothUser.Email == "" {
  878. missingFields = append(missingFields, "email")
  879. }
  880. if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" {
  881. missingFields = append(missingFields, "nickname")
  882. }
  883. if len(missingFields) > 0 {
  884. log.Error("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
  885. if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" {
  886. log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields")
  887. }
  888. err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
  889. ctx.ServerError("CreateUser", err)
  890. return
  891. }
  892. uname, err := getUserName(&gothUser)
  893. if err != nil {
  894. ctx.ServerError("UserSignIn", err)
  895. return
  896. }
  897. u = &user_model.User{
  898. Name: uname,
  899. FullName: gothUser.Name,
  900. Email: gothUser.Email,
  901. LoginType: auth.OAuth2,
  902. LoginSource: authSource.ID,
  903. LoginName: gothUser.UserID,
  904. }
  905. overwriteDefault := &user_model.CreateUserOverwriteOptions{
  906. IsActive: optional.Some(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
  907. }
  908. source := authSource.Cfg.(*oauth2.Source)
  909. isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser)
  910. u.IsAdmin = isAdmin.ValueOrDefault(false)
  911. u.IsRestricted = isRestricted.ValueOrDefault(false)
  912. if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
  913. // error already handled
  914. return
  915. }
  916. if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
  917. ctx.ServerError("SyncGroupsToTeams", err)
  918. return
  919. }
  920. } else {
  921. // no existing user is found, request attach or new account
  922. showLinkingLogin(ctx, gothUser)
  923. return
  924. }
  925. }
  926. handleOAuth2SignIn(ctx, authSource, u, gothUser)
  927. }
  928. func claimValueToStringSet(claimValue any) container.Set[string] {
  929. var groups []string
  930. switch rawGroup := claimValue.(type) {
  931. case []string:
  932. groups = rawGroup
  933. case []any:
  934. for _, group := range rawGroup {
  935. groups = append(groups, fmt.Sprintf("%s", group))
  936. }
  937. default:
  938. str := fmt.Sprintf("%s", rawGroup)
  939. groups = strings.Split(str, ",")
  940. }
  941. return container.SetOf(groups...)
  942. }
  943. func syncGroupsToTeams(ctx *context.Context, source *oauth2.Source, gothUser *goth.User, u *user_model.User) error {
  944. if source.GroupTeamMap != "" || source.GroupTeamMapRemoval {
  945. groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap)
  946. if err != nil {
  947. return err
  948. }
  949. groups := getClaimedGroups(source, gothUser)
  950. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, source.GroupTeamMapRemoval); err != nil {
  951. return err
  952. }
  953. }
  954. return nil
  955. }
  956. func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[string] {
  957. groupClaims, has := gothUser.RawData[source.GroupClaimName]
  958. if !has {
  959. return nil
  960. }
  961. return claimValueToStringSet(groupClaims)
  962. }
  963. func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) {
  964. groups := getClaimedGroups(source, gothUser)
  965. if source.AdminGroup != "" {
  966. isAdmin = optional.Some(groups.Contains(source.AdminGroup))
  967. }
  968. if source.RestrictedGroup != "" {
  969. isRestricted = optional.Some(groups.Contains(source.RestrictedGroup))
  970. }
  971. return isAdmin, isRestricted
  972. }
  973. func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
  974. if err := updateSession(ctx, nil, map[string]any{
  975. "linkAccountGothUser": gothUser,
  976. }); err != nil {
  977. ctx.ServerError("updateSession", err)
  978. return
  979. }
  980. ctx.Redirect(setting.AppSubURL + "/user/link_account")
  981. }
  982. func updateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
  983. if setting.OAuth2Client.UpdateAvatar && len(url) > 0 {
  984. resp, err := http.Get(url)
  985. if err == nil {
  986. defer func() {
  987. _ = resp.Body.Close()
  988. }()
  989. }
  990. // ignore any error
  991. if err == nil && resp.StatusCode == http.StatusOK {
  992. data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1))
  993. if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize {
  994. _ = user_service.UploadAvatar(ctx, u, data)
  995. }
  996. }
  997. }
  998. }
  999. func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) {
  1000. updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
  1001. needs2FA := false
  1002. if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
  1003. _, err := auth.GetTwoFactorByUID(ctx, u.ID)
  1004. if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
  1005. ctx.ServerError("UserSignIn", err)
  1006. return
  1007. }
  1008. needs2FA = err == nil
  1009. }
  1010. oauth2Source := source.Cfg.(*oauth2.Source)
  1011. groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap)
  1012. if err != nil {
  1013. ctx.ServerError("UnmarshalGroupTeamMapping", err)
  1014. return
  1015. }
  1016. groups := getClaimedGroups(oauth2Source, &gothUser)
  1017. // If this user is enrolled in 2FA and this source doesn't override it,
  1018. // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
  1019. if !needs2FA {
  1020. if err := updateSession(ctx, nil, map[string]any{
  1021. "uid": u.ID,
  1022. "uname": u.Name,
  1023. }); err != nil {
  1024. ctx.ServerError("updateSession", err)
  1025. return
  1026. }
  1027. // Clear whatever CSRF cookie has right now, force to generate a new one
  1028. ctx.Csrf.DeleteCookie(ctx)
  1029. opts := &user_service.UpdateOptions{
  1030. SetLastLogin: true,
  1031. }
  1032. opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
  1033. if err := user_service.UpdateUser(ctx, u, opts); err != nil {
  1034. ctx.ServerError("UpdateUser", err)
  1035. return
  1036. }
  1037. if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
  1038. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
  1039. ctx.ServerError("SyncGroupsToTeams", err)
  1040. return
  1041. }
  1042. }
  1043. // update external user information
  1044. if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil {
  1045. if !errors.Is(err, util.ErrNotExist) {
  1046. log.Error("UpdateExternalUser failed: %v", err)
  1047. }
  1048. }
  1049. if err := resetLocale(ctx, u); err != nil {
  1050. ctx.ServerError("resetLocale", err)
  1051. return
  1052. }
  1053. if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
  1054. middleware.DeleteRedirectToCookie(ctx.Resp)
  1055. ctx.RedirectToFirst(redirectTo)
  1056. return
  1057. }
  1058. ctx.Redirect(setting.AppSubURL + "/")
  1059. return
  1060. }
  1061. opts := &user_service.UpdateOptions{}
  1062. opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
  1063. if opts.IsAdmin.Has() || opts.IsRestricted.Has() {
  1064. if err := user_service.UpdateUser(ctx, u, opts); err != nil {
  1065. ctx.ServerError("UpdateUser", err)
  1066. return
  1067. }
  1068. }
  1069. if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
  1070. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
  1071. ctx.ServerError("SyncGroupsToTeams", err)
  1072. return
  1073. }
  1074. }
  1075. if err := updateSession(ctx, nil, map[string]any{
  1076. // User needs to use 2FA, save data and redirect to 2FA page.
  1077. "twofaUid": u.ID,
  1078. "twofaRemember": false,
  1079. }); err != nil {
  1080. ctx.ServerError("updateSession", err)
  1081. return
  1082. }
  1083. // If WebAuthn is enrolled -> Redirect to WebAuthn instead
  1084. regs, err := auth.GetWebAuthnCredentialsByUID(ctx, u.ID)
  1085. if err == nil && len(regs) > 0 {
  1086. ctx.Redirect(setting.AppSubURL + "/user/webauthn")
  1087. return
  1088. }
  1089. ctx.Redirect(setting.AppSubURL + "/user/two_factor")
  1090. }
  1091. // OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
  1092. // login the user
  1093. func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
  1094. oauth2Source := authSource.Cfg.(*oauth2.Source)
  1095. // Make sure that the response is not an error response.
  1096. errorName := request.FormValue("error")
  1097. if len(errorName) > 0 {
  1098. errorDescription := request.FormValue("error_description")
  1099. // Delete the goth session
  1100. err := gothic.Logout(response, request)
  1101. if err != nil {
  1102. return nil, goth.User{}, err
  1103. }
  1104. return nil, goth.User{}, errCallback{
  1105. Code: errorName,
  1106. Description: errorDescription,
  1107. }
  1108. }
  1109. // Proceed to authenticate through goth.
  1110. gothUser, err := oauth2Source.Callback(request, response)
  1111. if err != nil {
  1112. if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
  1113. log.Error("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)
  1114. 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)
  1115. }
  1116. return nil, goth.User{}, err
  1117. }
  1118. if oauth2Source.RequiredClaimName != "" {
  1119. claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
  1120. if !has {
  1121. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  1122. }
  1123. if oauth2Source.RequiredClaimValue != "" {
  1124. groups := claimValueToStringSet(claimInterface)
  1125. if !groups.Contains(oauth2Source.RequiredClaimValue) {
  1126. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  1127. }
  1128. }
  1129. }
  1130. user := &user_model.User{
  1131. LoginName: gothUser.UserID,
  1132. LoginType: auth.OAuth2,
  1133. LoginSource: authSource.ID,
  1134. }
  1135. hasUser, err := user_model.GetUser(ctx, user)
  1136. if err != nil {
  1137. return nil, goth.User{}, err
  1138. }
  1139. if hasUser {
  1140. return user, gothUser, nil
  1141. }
  1142. // search in external linked users
  1143. externalLoginUser := &user_model.ExternalLoginUser{
  1144. ExternalID: gothUser.UserID,
  1145. LoginSourceID: authSource.ID,
  1146. }
  1147. hasUser, err = user_model.GetExternalLogin(request.Context(), externalLoginUser)
  1148. if err != nil {
  1149. return nil, goth.User{}, err
  1150. }
  1151. if hasUser {
  1152. user, err = user_model.GetUserByID(request.Context(), externalLoginUser.UserID)
  1153. return user, gothUser, err
  1154. }
  1155. // no user found to login
  1156. return nil, gothUser, nil
  1157. }