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.

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