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 42KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  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/json"
  23. "code.gitea.io/gitea/modules/log"
  24. "code.gitea.io/gitea/modules/optional"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/timeutil"
  27. "code.gitea.io/gitea/modules/util"
  28. "code.gitea.io/gitea/modules/web"
  29. "code.gitea.io/gitea/modules/web/middleware"
  30. auth_service "code.gitea.io/gitea/services/auth"
  31. source_service "code.gitea.io/gitea/services/auth/source"
  32. "code.gitea.io/gitea/services/auth/source/oauth2"
  33. "code.gitea.io/gitea/services/context"
  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 and the application is confidential.
  430. // I.e. always require authorization for public clients as recommended by RFC 6749 Section 10.2
  431. if app.ConfidentialClient && grant != nil {
  432. code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
  433. if err != nil {
  434. handleServerError(ctx, form.State, form.RedirectURI)
  435. return
  436. }
  437. redirect, err := code.GenerateRedirectURI(form.State)
  438. if err != nil {
  439. handleServerError(ctx, form.State, form.RedirectURI)
  440. return
  441. }
  442. // Update nonce to reflect the new session
  443. if len(form.Nonce) > 0 {
  444. err := grant.SetNonce(ctx, form.Nonce)
  445. if err != nil {
  446. log.Error("Unable to update nonce: %v", err)
  447. }
  448. }
  449. ctx.Redirect(redirect.String())
  450. return
  451. }
  452. // show authorize page to grant access
  453. ctx.Data["Application"] = app
  454. ctx.Data["RedirectURI"] = form.RedirectURI
  455. ctx.Data["State"] = form.State
  456. ctx.Data["Scope"] = form.Scope
  457. ctx.Data["Nonce"] = form.Nonce
  458. if user != nil {
  459. ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">@%s</a>`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name)))
  460. } else {
  461. ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName)))
  462. }
  463. ctx.Data["ApplicationRedirectDomainHTML"] = template.HTML("<strong>" + html.EscapeString(form.RedirectURI) + "</strong>")
  464. // TODO document SESSION <=> FORM
  465. err = ctx.Session.Set("client_id", app.ClientID)
  466. if err != nil {
  467. handleServerError(ctx, form.State, form.RedirectURI)
  468. log.Error(err.Error())
  469. return
  470. }
  471. err = ctx.Session.Set("redirect_uri", form.RedirectURI)
  472. if err != nil {
  473. handleServerError(ctx, form.State, form.RedirectURI)
  474. log.Error(err.Error())
  475. return
  476. }
  477. err = ctx.Session.Set("state", form.State)
  478. if err != nil {
  479. handleServerError(ctx, form.State, form.RedirectURI)
  480. log.Error(err.Error())
  481. return
  482. }
  483. // Here we're just going to try to release the session early
  484. if err := ctx.Session.Release(); err != nil {
  485. // we'll tolerate errors here as they *should* get saved elsewhere
  486. log.Error("Unable to save changes to the session: %v", err)
  487. }
  488. ctx.HTML(http.StatusOK, tplGrantAccess)
  489. }
  490. // GrantApplicationOAuth manages the post request submitted when a user grants access to an application
  491. func GrantApplicationOAuth(ctx *context.Context) {
  492. form := web.GetForm(ctx).(*forms.GrantApplicationForm)
  493. if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
  494. ctx.Session.Get("redirect_uri") != form.RedirectURI {
  495. ctx.Error(http.StatusBadRequest)
  496. return
  497. }
  498. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  499. if err != nil {
  500. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  501. return
  502. }
  503. grant, err := app.CreateGrant(ctx, ctx.Doer.ID, form.Scope)
  504. if err != nil {
  505. handleAuthorizeError(ctx, AuthorizeError{
  506. State: form.State,
  507. ErrorDescription: "cannot create grant for user",
  508. ErrorCode: ErrorCodeServerError,
  509. }, form.RedirectURI)
  510. return
  511. }
  512. if len(form.Nonce) > 0 {
  513. err := grant.SetNonce(ctx, form.Nonce)
  514. if err != nil {
  515. log.Error("Unable to update nonce: %v", err)
  516. }
  517. }
  518. var codeChallenge, codeChallengeMethod string
  519. codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
  520. codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
  521. code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, codeChallenge, codeChallengeMethod)
  522. if err != nil {
  523. handleServerError(ctx, form.State, form.RedirectURI)
  524. return
  525. }
  526. redirect, err := code.GenerateRedirectURI(form.State)
  527. if err != nil {
  528. handleServerError(ctx, form.State, form.RedirectURI)
  529. return
  530. }
  531. ctx.Redirect(redirect.String(), http.StatusSeeOther)
  532. }
  533. // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
  534. func OIDCWellKnown(ctx *context.Context) {
  535. ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
  536. ctx.JSONTemplate("user/auth/oidc_wellknown")
  537. }
  538. // OIDCKeys generates the JSON Web Key Set
  539. func OIDCKeys(ctx *context.Context) {
  540. jwk, err := oauth2.DefaultSigningKey.ToJWK()
  541. if err != nil {
  542. log.Error("Error converting signing key to JWK: %v", err)
  543. ctx.Error(http.StatusInternalServerError)
  544. return
  545. }
  546. jwk["use"] = "sig"
  547. jwks := map[string][]map[string]string{
  548. "keys": {
  549. jwk,
  550. },
  551. }
  552. ctx.Resp.Header().Set("Content-Type", "application/json")
  553. enc := json.NewEncoder(ctx.Resp)
  554. if err := enc.Encode(jwks); err != nil {
  555. log.Error("Failed to encode representation as json. Error: %v", err)
  556. }
  557. }
  558. // AccessTokenOAuth manages all access token requests by the client
  559. func AccessTokenOAuth(ctx *context.Context) {
  560. form := *web.GetForm(ctx).(*forms.AccessTokenForm)
  561. // 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
  562. if form.ClientID == "" || form.ClientSecret == "" {
  563. authHeader := ctx.Req.Header.Get("Authorization")
  564. authContent := strings.SplitN(authHeader, " ", 2)
  565. if len(authContent) == 2 && authContent[0] == "Basic" {
  566. payload, err := base64.StdEncoding.DecodeString(authContent[1])
  567. if err != nil {
  568. handleAccessTokenError(ctx, AccessTokenError{
  569. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  570. ErrorDescription: "cannot parse basic auth header",
  571. })
  572. return
  573. }
  574. pair := strings.SplitN(string(payload), ":", 2)
  575. if len(pair) != 2 {
  576. handleAccessTokenError(ctx, AccessTokenError{
  577. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  578. ErrorDescription: "cannot parse basic auth header",
  579. })
  580. return
  581. }
  582. if form.ClientID != "" && form.ClientID != pair[0] {
  583. handleAccessTokenError(ctx, AccessTokenError{
  584. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  585. ErrorDescription: "client_id in request body inconsistent with Authorization header",
  586. })
  587. return
  588. }
  589. form.ClientID = pair[0]
  590. if form.ClientSecret != "" && form.ClientSecret != pair[1] {
  591. handleAccessTokenError(ctx, AccessTokenError{
  592. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  593. ErrorDescription: "client_secret in request body inconsistent with Authorization header",
  594. })
  595. return
  596. }
  597. form.ClientSecret = pair[1]
  598. }
  599. }
  600. serverKey := oauth2.DefaultSigningKey
  601. clientKey := serverKey
  602. if serverKey.IsSymmetric() {
  603. var err error
  604. clientKey, err = oauth2.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret))
  605. if err != nil {
  606. handleAccessTokenError(ctx, AccessTokenError{
  607. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  608. ErrorDescription: "Error creating signing key",
  609. })
  610. return
  611. }
  612. }
  613. switch form.GrantType {
  614. case "refresh_token":
  615. handleRefreshToken(ctx, form, serverKey, clientKey)
  616. case "authorization_code":
  617. handleAuthorizationCode(ctx, form, serverKey, clientKey)
  618. default:
  619. handleAccessTokenError(ctx, AccessTokenError{
  620. ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
  621. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  622. })
  623. }
  624. }
  625. func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
  626. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  627. if err != nil {
  628. handleAccessTokenError(ctx, AccessTokenError{
  629. ErrorCode: AccessTokenErrorCodeInvalidClient,
  630. ErrorDescription: fmt.Sprintf("cannot load client with client id: %q", form.ClientID),
  631. })
  632. return
  633. }
  634. // "The authorization server MUST ... require client authentication for confidential clients"
  635. // https://datatracker.ietf.org/doc/html/rfc6749#section-6
  636. if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  637. errorDescription := "invalid client secret"
  638. if form.ClientSecret == "" {
  639. errorDescription = "invalid empty client secret"
  640. }
  641. // "invalid_client ... Client authentication failed"
  642. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  643. handleAccessTokenError(ctx, AccessTokenError{
  644. ErrorCode: AccessTokenErrorCodeInvalidClient,
  645. ErrorDescription: errorDescription,
  646. })
  647. return
  648. }
  649. token, err := oauth2.ParseToken(form.RefreshToken, serverKey)
  650. if err != nil {
  651. handleAccessTokenError(ctx, AccessTokenError{
  652. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  653. ErrorDescription: "unable to parse refresh token",
  654. })
  655. return
  656. }
  657. // get grant before increasing counter
  658. grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID)
  659. if err != nil || grant == nil {
  660. handleAccessTokenError(ctx, AccessTokenError{
  661. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  662. ErrorDescription: "grant does not exist",
  663. })
  664. return
  665. }
  666. // check if token got already used
  667. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  668. handleAccessTokenError(ctx, AccessTokenError{
  669. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  670. ErrorDescription: "token was already used",
  671. })
  672. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  673. return
  674. }
  675. accessToken, tokenErr := newAccessTokenResponse(ctx, grant, serverKey, clientKey)
  676. if tokenErr != nil {
  677. handleAccessTokenError(ctx, *tokenErr)
  678. return
  679. }
  680. ctx.JSON(http.StatusOK, accessToken)
  681. }
  682. func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
  683. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  684. if err != nil {
  685. handleAccessTokenError(ctx, AccessTokenError{
  686. ErrorCode: AccessTokenErrorCodeInvalidClient,
  687. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  688. })
  689. return
  690. }
  691. if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  692. errorDescription := "invalid client secret"
  693. if form.ClientSecret == "" {
  694. errorDescription = "invalid empty client secret"
  695. }
  696. handleAccessTokenError(ctx, AccessTokenError{
  697. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  698. ErrorDescription: errorDescription,
  699. })
  700. return
  701. }
  702. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  703. handleAccessTokenError(ctx, AccessTokenError{
  704. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  705. ErrorDescription: "unexpected redirect URI",
  706. })
  707. return
  708. }
  709. authorizationCode, err := auth.GetOAuth2AuthorizationByCode(ctx, form.Code)
  710. if err != nil || authorizationCode == nil {
  711. handleAccessTokenError(ctx, AccessTokenError{
  712. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  713. ErrorDescription: "client is not authorized",
  714. })
  715. return
  716. }
  717. // check if code verifier authorizes the client, PKCE support
  718. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  719. handleAccessTokenError(ctx, AccessTokenError{
  720. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  721. ErrorDescription: "failed PKCE code challenge",
  722. })
  723. return
  724. }
  725. // check if granted for this application
  726. if authorizationCode.Grant.ApplicationID != app.ID {
  727. handleAccessTokenError(ctx, AccessTokenError{
  728. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  729. ErrorDescription: "invalid grant",
  730. })
  731. return
  732. }
  733. // remove token from database to deny duplicate usage
  734. if err := authorizationCode.Invalidate(ctx); err != nil {
  735. handleAccessTokenError(ctx, AccessTokenError{
  736. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  737. ErrorDescription: "cannot proceed your request",
  738. })
  739. }
  740. resp, tokenErr := newAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey)
  741. if tokenErr != nil {
  742. handleAccessTokenError(ctx, *tokenErr)
  743. return
  744. }
  745. // send successful response
  746. ctx.JSON(http.StatusOK, resp)
  747. }
  748. func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
  749. ctx.JSON(http.StatusBadRequest, acErr)
  750. }
  751. func handleServerError(ctx *context.Context, state, redirectURI string) {
  752. handleAuthorizeError(ctx, AuthorizeError{
  753. ErrorCode: ErrorCodeServerError,
  754. ErrorDescription: "A server error occurred",
  755. State: state,
  756. }, redirectURI)
  757. }
  758. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  759. if redirectURI == "" {
  760. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  761. ctx.Data["Error"] = authErr
  762. ctx.HTML(http.StatusBadRequest, tplGrantError)
  763. return
  764. }
  765. redirect, err := url.Parse(redirectURI)
  766. if err != nil {
  767. ctx.ServerError("url.Parse", err)
  768. return
  769. }
  770. q := redirect.Query()
  771. q.Set("error", string(authErr.ErrorCode))
  772. q.Set("error_description", authErr.ErrorDescription)
  773. q.Set("state", authErr.State)
  774. redirect.RawQuery = q.Encode()
  775. ctx.Redirect(redirect.String(), http.StatusSeeOther)
  776. }
  777. // SignInOAuth handles the OAuth2 login buttons
  778. func SignInOAuth(ctx *context.Context) {
  779. provider := ctx.Params(":provider")
  780. authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
  781. if err != nil {
  782. ctx.ServerError("SignIn", err)
  783. return
  784. }
  785. redirectTo := ctx.FormString("redirect_to")
  786. if len(redirectTo) > 0 {
  787. middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
  788. }
  789. // try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
  790. user, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp)
  791. if err == nil && user != nil {
  792. // we got the user without going through the whole OAuth2 authentication flow again
  793. handleOAuth2SignIn(ctx, authSource, user, gothUser)
  794. return
  795. }
  796. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  797. if strings.Contains(err.Error(), "no provider for ") {
  798. if err = oauth2.ResetOAuth2(ctx); err != nil {
  799. ctx.ServerError("SignIn", err)
  800. return
  801. }
  802. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  803. ctx.ServerError("SignIn", err)
  804. }
  805. return
  806. }
  807. ctx.ServerError("SignIn", err)
  808. }
  809. // redirect is done in oauth2.Auth
  810. }
  811. // SignInOAuthCallback handles the callback from the given provider
  812. func SignInOAuthCallback(ctx *context.Context) {
  813. provider := ctx.Params(":provider")
  814. if ctx.Req.FormValue("error") != "" {
  815. var errorKeyValues []string
  816. for k, vv := range ctx.Req.Form {
  817. for _, v := range vv {
  818. errorKeyValues = append(errorKeyValues, fmt.Sprintf("%s = %s", html.EscapeString(k), html.EscapeString(v)))
  819. }
  820. }
  821. sort.Strings(errorKeyValues)
  822. ctx.Flash.Error(strings.Join(errorKeyValues, "<br>"), true)
  823. }
  824. // first look if the provider is still active
  825. authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
  826. if err != nil {
  827. ctx.ServerError("SignIn", err)
  828. return
  829. }
  830. if authSource == nil {
  831. ctx.ServerError("SignIn", errors.New("no valid provider found, check configured callback url in provider"))
  832. return
  833. }
  834. u, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp)
  835. if err != nil {
  836. if user_model.IsErrUserProhibitLogin(err) {
  837. uplerr := err.(user_model.ErrUserProhibitLogin)
  838. log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
  839. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  840. ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
  841. return
  842. }
  843. if callbackErr, ok := err.(errCallback); ok {
  844. log.Info("Failed OAuth callback: (%v) %v", callbackErr.Code, callbackErr.Description)
  845. switch callbackErr.Code {
  846. case "access_denied":
  847. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.access_denied"))
  848. case "temporarily_unavailable":
  849. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.temporarily_unavailable"))
  850. default:
  851. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error"))
  852. }
  853. ctx.Redirect(setting.AppSubURL + "/user/login")
  854. return
  855. }
  856. if err, ok := err.(*go_oauth2.RetrieveError); ok {
  857. ctx.Flash.Error("OAuth2 RetrieveError: "+err.Error(), true)
  858. }
  859. ctx.ServerError("UserSignIn", err)
  860. return
  861. }
  862. if u == nil {
  863. if ctx.Doer != nil {
  864. // attach user to the current signed-in user
  865. err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
  866. if err != nil {
  867. ctx.ServerError("UserLinkAccount", err)
  868. return
  869. }
  870. ctx.Redirect(setting.AppSubURL + "/user/settings/security")
  871. return
  872. } else if !setting.Service.AllowOnlyInternalRegistration && setting.OAuth2Client.EnableAutoRegistration {
  873. // create new user with details from oauth2 provider
  874. var missingFields []string
  875. if gothUser.UserID == "" {
  876. missingFields = append(missingFields, "sub")
  877. }
  878. if gothUser.Email == "" {
  879. missingFields = append(missingFields, "email")
  880. }
  881. uname, err := extractUserNameFromOAuth2(&gothUser)
  882. if err != nil {
  883. ctx.ServerError("UserSignIn", err)
  884. return
  885. }
  886. if uname == "" {
  887. if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname {
  888. missingFields = append(missingFields, "nickname")
  889. } else if setting.OAuth2Client.Username == setting.OAuth2UsernamePreferredUsername {
  890. missingFields = append(missingFields, "preferred_username")
  891. } // else: "UserID" and "Email" have been handled above separately
  892. }
  893. if len(missingFields) > 0 {
  894. log.Error(`OAuth2 auto registration (ENABLE_AUTO_REGISTRATION) is enabled but OAuth2 provider %q doesn't return required fields: %s. `+
  895. `Suggest to: disable auto registration, or make OPENID_CONNECT_SCOPES (for OpenIDConnect) / Authentication Source Scopes (for Admin panel) to request all required fields, and the fields shouldn't be empty.`,
  896. authSource.Name, strings.Join(missingFields, ","))
  897. // The RawData is the only way to pass the missing fields to the another page at the moment, other ways all have various problems:
  898. // by session or cookie: difficult to clean or reset; by URL: could be injected with uncontrollable content; by ctx.Flash: the link_account page is a mess ...
  899. // Since the RawData is for the provider's data, so we need to use our own prefix here to avoid conflict.
  900. if gothUser.RawData == nil {
  901. gothUser.RawData = make(map[string]any)
  902. }
  903. gothUser.RawData["__giteaAutoRegMissingFields"] = missingFields
  904. showLinkingLogin(ctx, gothUser)
  905. return
  906. }
  907. u = &user_model.User{
  908. Name: uname,
  909. FullName: gothUser.Name,
  910. Email: gothUser.Email,
  911. LoginType: auth.OAuth2,
  912. LoginSource: authSource.ID,
  913. LoginName: gothUser.UserID,
  914. }
  915. overwriteDefault := &user_model.CreateUserOverwriteOptions{
  916. IsActive: optional.Some(!setting.OAuth2Client.RegisterEmailConfirm && !setting.Service.RegisterManualConfirm),
  917. }
  918. source := authSource.Cfg.(*oauth2.Source)
  919. isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser)
  920. u.IsAdmin = isAdmin.ValueOrDefault(false)
  921. u.IsRestricted = isRestricted.ValueOrDefault(false)
  922. if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
  923. // error already handled
  924. return
  925. }
  926. if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
  927. ctx.ServerError("SyncGroupsToTeams", err)
  928. return
  929. }
  930. } else {
  931. // no existing user is found, request attach or new account
  932. showLinkingLogin(ctx, gothUser)
  933. return
  934. }
  935. }
  936. handleOAuth2SignIn(ctx, authSource, u, gothUser)
  937. }
  938. func claimValueToStringSet(claimValue any) container.Set[string] {
  939. var groups []string
  940. switch rawGroup := claimValue.(type) {
  941. case []string:
  942. groups = rawGroup
  943. case []any:
  944. for _, group := range rawGroup {
  945. groups = append(groups, fmt.Sprintf("%s", group))
  946. }
  947. default:
  948. str := fmt.Sprintf("%s", rawGroup)
  949. groups = strings.Split(str, ",")
  950. }
  951. return container.SetOf(groups...)
  952. }
  953. func syncGroupsToTeams(ctx *context.Context, source *oauth2.Source, gothUser *goth.User, u *user_model.User) error {
  954. if source.GroupTeamMap != "" || source.GroupTeamMapRemoval {
  955. groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap)
  956. if err != nil {
  957. return err
  958. }
  959. groups := getClaimedGroups(source, gothUser)
  960. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, source.GroupTeamMapRemoval); err != nil {
  961. return err
  962. }
  963. }
  964. return nil
  965. }
  966. func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[string] {
  967. groupClaims, has := gothUser.RawData[source.GroupClaimName]
  968. if !has {
  969. return nil
  970. }
  971. return claimValueToStringSet(groupClaims)
  972. }
  973. func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *goth.User) (isAdmin, isRestricted optional.Option[bool]) {
  974. groups := getClaimedGroups(source, gothUser)
  975. if source.AdminGroup != "" {
  976. isAdmin = optional.Some(groups.Contains(source.AdminGroup))
  977. }
  978. if source.RestrictedGroup != "" {
  979. isRestricted = optional.Some(groups.Contains(source.RestrictedGroup))
  980. }
  981. return isAdmin, isRestricted
  982. }
  983. func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
  984. if err := updateSession(ctx, nil, map[string]any{
  985. "linkAccountGothUser": gothUser,
  986. }); err != nil {
  987. ctx.ServerError("updateSession", err)
  988. return
  989. }
  990. ctx.Redirect(setting.AppSubURL + "/user/link_account")
  991. }
  992. func updateAvatarIfNeed(ctx *context.Context, url string, u *user_model.User) {
  993. if setting.OAuth2Client.UpdateAvatar && len(url) > 0 {
  994. resp, err := http.Get(url)
  995. if err == nil {
  996. defer func() {
  997. _ = resp.Body.Close()
  998. }()
  999. }
  1000. // ignore any error
  1001. if err == nil && resp.StatusCode == http.StatusOK {
  1002. data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1))
  1003. if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize {
  1004. _ = user_service.UploadAvatar(ctx, u, data)
  1005. }
  1006. }
  1007. }
  1008. }
  1009. func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) {
  1010. updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
  1011. needs2FA := false
  1012. if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
  1013. _, err := auth.GetTwoFactorByUID(ctx, u.ID)
  1014. if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
  1015. ctx.ServerError("UserSignIn", err)
  1016. return
  1017. }
  1018. needs2FA = err == nil
  1019. }
  1020. oauth2Source := source.Cfg.(*oauth2.Source)
  1021. groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap)
  1022. if err != nil {
  1023. ctx.ServerError("UnmarshalGroupTeamMapping", err)
  1024. return
  1025. }
  1026. groups := getClaimedGroups(oauth2Source, &gothUser)
  1027. // If this user is enrolled in 2FA and this source doesn't override it,
  1028. // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
  1029. if !needs2FA {
  1030. if err := updateSession(ctx, nil, map[string]any{
  1031. "uid": u.ID,
  1032. "uname": u.Name,
  1033. }); err != nil {
  1034. ctx.ServerError("updateSession", err)
  1035. return
  1036. }
  1037. // Clear whatever CSRF cookie has right now, force to generate a new one
  1038. ctx.Csrf.DeleteCookie(ctx)
  1039. opts := &user_service.UpdateOptions{
  1040. SetLastLogin: true,
  1041. }
  1042. opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
  1043. if err := user_service.UpdateUser(ctx, u, opts); err != nil {
  1044. ctx.ServerError("UpdateUser", err)
  1045. return
  1046. }
  1047. if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
  1048. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
  1049. ctx.ServerError("SyncGroupsToTeams", err)
  1050. return
  1051. }
  1052. }
  1053. // update external user information
  1054. if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil {
  1055. if !errors.Is(err, util.ErrNotExist) {
  1056. log.Error("UpdateExternalUser failed: %v", err)
  1057. }
  1058. }
  1059. if err := resetLocale(ctx, u); err != nil {
  1060. ctx.ServerError("resetLocale", err)
  1061. return
  1062. }
  1063. if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
  1064. middleware.DeleteRedirectToCookie(ctx.Resp)
  1065. ctx.RedirectToCurrentSite(redirectTo)
  1066. return
  1067. }
  1068. ctx.Redirect(setting.AppSubURL + "/")
  1069. return
  1070. }
  1071. opts := &user_service.UpdateOptions{}
  1072. opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser)
  1073. if opts.IsAdmin.Has() || opts.IsRestricted.Has() {
  1074. if err := user_service.UpdateUser(ctx, u, opts); err != nil {
  1075. ctx.ServerError("UpdateUser", err)
  1076. return
  1077. }
  1078. }
  1079. if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval {
  1080. if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil {
  1081. ctx.ServerError("SyncGroupsToTeams", err)
  1082. return
  1083. }
  1084. }
  1085. if err := updateSession(ctx, nil, map[string]any{
  1086. // User needs to use 2FA, save data and redirect to 2FA page.
  1087. "twofaUid": u.ID,
  1088. "twofaRemember": false,
  1089. }); err != nil {
  1090. ctx.ServerError("updateSession", err)
  1091. return
  1092. }
  1093. // If WebAuthn is enrolled -> Redirect to WebAuthn instead
  1094. regs, err := auth.GetWebAuthnCredentialsByUID(ctx, u.ID)
  1095. if err == nil && len(regs) > 0 {
  1096. ctx.Redirect(setting.AppSubURL + "/user/webauthn")
  1097. return
  1098. }
  1099. ctx.Redirect(setting.AppSubURL + "/user/two_factor")
  1100. }
  1101. // OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
  1102. // login the user
  1103. func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
  1104. oauth2Source := authSource.Cfg.(*oauth2.Source)
  1105. // Make sure that the response is not an error response.
  1106. errorName := request.FormValue("error")
  1107. if len(errorName) > 0 {
  1108. errorDescription := request.FormValue("error_description")
  1109. // Delete the goth session
  1110. err := gothic.Logout(response, request)
  1111. if err != nil {
  1112. return nil, goth.User{}, err
  1113. }
  1114. return nil, goth.User{}, errCallback{
  1115. Code: errorName,
  1116. Description: errorDescription,
  1117. }
  1118. }
  1119. // Proceed to authenticate through goth.
  1120. gothUser, err := oauth2Source.Callback(request, response)
  1121. if err != nil {
  1122. if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
  1123. 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)
  1124. 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)
  1125. }
  1126. return nil, goth.User{}, err
  1127. }
  1128. if oauth2Source.RequiredClaimName != "" {
  1129. claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
  1130. if !has {
  1131. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  1132. }
  1133. if oauth2Source.RequiredClaimValue != "" {
  1134. groups := claimValueToStringSet(claimInterface)
  1135. if !groups.Contains(oauth2Source.RequiredClaimValue) {
  1136. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  1137. }
  1138. }
  1139. }
  1140. user := &user_model.User{
  1141. LoginName: gothUser.UserID,
  1142. LoginType: auth.OAuth2,
  1143. LoginSource: authSource.ID,
  1144. }
  1145. hasUser, err := user_model.GetUser(ctx, user)
  1146. if err != nil {
  1147. return nil, goth.User{}, err
  1148. }
  1149. if hasUser {
  1150. return user, gothUser, nil
  1151. }
  1152. // search in external linked users
  1153. externalLoginUser := &user_model.ExternalLoginUser{
  1154. ExternalID: gothUser.UserID,
  1155. LoginSourceID: authSource.ID,
  1156. }
  1157. hasUser, err = user_model.GetExternalLogin(request.Context(), externalLoginUser)
  1158. if err != nil {
  1159. return nil, goth.User{}, err
  1160. }
  1161. if hasUser {
  1162. user, err = user_model.GetUserByID(request.Context(), externalLoginUser.UserID)
  1163. return user, gothUser, err
  1164. }
  1165. // no user found to login
  1166. return nil, gothUser, nil
  1167. }