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

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