gitea源码

oauth2_provider.go 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "fmt"
  6. "html"
  7. "html/template"
  8. "net/http"
  9. "net/url"
  10. "strconv"
  11. "strings"
  12. "code.gitea.io/gitea/models/auth"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/auth/httpauth"
  15. "code.gitea.io/gitea/modules/json"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/templates"
  19. "code.gitea.io/gitea/modules/web"
  20. auth_service "code.gitea.io/gitea/services/auth"
  21. "code.gitea.io/gitea/services/context"
  22. "code.gitea.io/gitea/services/forms"
  23. "code.gitea.io/gitea/services/oauth2_provider"
  24. "gitea.com/go-chi/binding"
  25. jwt "github.com/golang-jwt/jwt/v5"
  26. )
  27. const (
  28. tplGrantAccess templates.TplName = "user/auth/grant"
  29. tplGrantError templates.TplName = "user/auth/grant_error"
  30. )
  31. // TODO move error and responses to SDK or models
  32. // AuthorizeErrorCode represents an error code specified in RFC 6749
  33. // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
  34. type AuthorizeErrorCode string
  35. const (
  36. // ErrorCodeInvalidRequest represents the according error in RFC 6749
  37. ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request"
  38. // ErrorCodeUnauthorizedClient represents the according error in RFC 6749
  39. ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client"
  40. // ErrorCodeAccessDenied represents the according error in RFC 6749
  41. ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied"
  42. // ErrorCodeUnsupportedResponseType represents the according error in RFC 6749
  43. ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type"
  44. // ErrorCodeInvalidScope represents the according error in RFC 6749
  45. ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope"
  46. // ErrorCodeServerError represents the according error in RFC 6749
  47. ErrorCodeServerError AuthorizeErrorCode = "server_error"
  48. // ErrorCodeTemporaryUnavailable represents the according error in RFC 6749
  49. ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable"
  50. )
  51. // AuthorizeError represents an error type specified in RFC 6749
  52. // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2.1
  53. type AuthorizeError struct {
  54. ErrorCode AuthorizeErrorCode `json:"error" form:"error"`
  55. ErrorDescription string
  56. State string
  57. }
  58. // Error returns the error message
  59. func (err AuthorizeError) Error() string {
  60. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  61. }
  62. // errCallback represents a oauth2 callback error
  63. type errCallback struct {
  64. Code string
  65. Description string
  66. }
  67. func (err errCallback) Error() string {
  68. return err.Description
  69. }
  70. type userInfoResponse struct {
  71. Sub string `json:"sub"`
  72. Name string `json:"name"`
  73. PreferredUsername string `json:"preferred_username"`
  74. Email string `json:"email"`
  75. Picture string `json:"picture"`
  76. Groups []string `json:"groups"`
  77. }
  78. // InfoOAuth manages request for userinfo endpoint
  79. func InfoOAuth(ctx *context.Context) {
  80. if ctx.Doer == nil || ctx.Data["AuthedMethod"] != (&auth_service.OAuth2{}).Name() {
  81. ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm="Gitea OAuth2"`)
  82. ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
  83. return
  84. }
  85. response := &userInfoResponse{
  86. Sub: strconv.FormatInt(ctx.Doer.ID, 10),
  87. Name: ctx.Doer.DisplayName(),
  88. PreferredUsername: ctx.Doer.Name,
  89. Email: ctx.Doer.Email,
  90. Picture: ctx.Doer.AvatarLink(ctx),
  91. }
  92. var accessTokenScope auth.AccessTokenScope
  93. if auHead := ctx.Req.Header.Get("Authorization"); auHead != "" {
  94. if parsed, ok := httpauth.ParseAuthorizationHeader(auHead); ok && parsed.BearerToken != nil {
  95. accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, parsed.BearerToken.Token)
  96. }
  97. }
  98. // since version 1.22 does not verify if groups should be public-only,
  99. // onlyPublicGroups will be set only if 'public-only' is included in a valid scope
  100. onlyPublicGroups, _ := accessTokenScope.PublicOnly()
  101. groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer, onlyPublicGroups)
  102. if err != nil {
  103. ctx.ServerError("Oauth groups for user", err)
  104. return
  105. }
  106. response.Groups = groups
  107. ctx.JSON(http.StatusOK, response)
  108. }
  109. // IntrospectOAuth introspects an oauth token
  110. func IntrospectOAuth(ctx *context.Context) {
  111. clientIDValid := false
  112. authHeader := ctx.Req.Header.Get("Authorization")
  113. if parsed, ok := httpauth.ParseAuthorizationHeader(authHeader); ok && parsed.BasicAuth != nil {
  114. clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password
  115. app, err := auth.GetOAuth2ApplicationByClientID(ctx, clientID)
  116. if err != nil && !auth.IsErrOauthClientIDInvalid(err) {
  117. // this is likely a database error; log it and respond without details
  118. log.Error("Error retrieving client_id: %v", err)
  119. ctx.HTTPError(http.StatusInternalServerError)
  120. return
  121. }
  122. clientIDValid = err == nil && app.ValidateClientSecret([]byte(clientSecret))
  123. }
  124. if !clientIDValid {
  125. ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea OAuth2"`)
  126. ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
  127. return
  128. }
  129. var response struct {
  130. Active bool `json:"active"`
  131. Scope string `json:"scope,omitempty"`
  132. Username string `json:"username,omitempty"`
  133. jwt.RegisteredClaims
  134. }
  135. form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
  136. token, err := oauth2_provider.ParseToken(form.Token, oauth2_provider.DefaultSigningKey)
  137. if err == nil {
  138. grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID)
  139. if err == nil && grant != nil {
  140. app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID)
  141. if err == nil && app != nil {
  142. response.Active = true
  143. response.Scope = grant.Scope
  144. response.RegisteredClaims = oauth2_provider.NewJwtRegisteredClaimsFromUser(app.ClientID, grant.UserID, nil /*exp*/)
  145. }
  146. if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil {
  147. response.Username = user.Name
  148. }
  149. }
  150. }
  151. ctx.JSON(http.StatusOK, response)
  152. }
  153. // AuthorizeOAuth manages authorize requests
  154. func AuthorizeOAuth(ctx *context.Context) {
  155. form := web.GetForm(ctx).(*forms.AuthorizationForm)
  156. errs := binding.Errors{}
  157. errs = form.Validate(ctx.Req, errs)
  158. if len(errs) > 0 {
  159. errstring := ""
  160. for _, e := range errs {
  161. errstring += e.Error() + "\n"
  162. }
  163. ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
  164. return
  165. }
  166. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  167. if err != nil {
  168. if auth.IsErrOauthClientIDInvalid(err) {
  169. handleAuthorizeError(ctx, AuthorizeError{
  170. ErrorCode: ErrorCodeUnauthorizedClient,
  171. ErrorDescription: "Client ID not registered",
  172. State: form.State,
  173. }, "")
  174. return
  175. }
  176. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  177. return
  178. }
  179. var user *user_model.User
  180. if app.UID != 0 {
  181. user, err = user_model.GetUserByID(ctx, app.UID)
  182. if err != nil {
  183. ctx.ServerError("GetUserByID", err)
  184. return
  185. }
  186. }
  187. if !app.ContainsRedirectURI(form.RedirectURI) {
  188. handleAuthorizeError(ctx, AuthorizeError{
  189. ErrorCode: ErrorCodeInvalidRequest,
  190. ErrorDescription: "Unregistered Redirect URI",
  191. State: form.State,
  192. }, "")
  193. return
  194. }
  195. if form.ResponseType != "code" {
  196. handleAuthorizeError(ctx, AuthorizeError{
  197. ErrorCode: ErrorCodeUnsupportedResponseType,
  198. ErrorDescription: "Only code response type is supported.",
  199. State: form.State,
  200. }, form.RedirectURI)
  201. return
  202. }
  203. // pkce support
  204. switch form.CodeChallengeMethod {
  205. case "S256":
  206. case "plain":
  207. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
  208. handleAuthorizeError(ctx, AuthorizeError{
  209. ErrorCode: ErrorCodeServerError,
  210. ErrorDescription: "cannot set code challenge method",
  211. State: form.State,
  212. }, form.RedirectURI)
  213. return
  214. }
  215. if err := ctx.Session.Set("CodeChallenge", form.CodeChallenge); err != nil {
  216. handleAuthorizeError(ctx, AuthorizeError{
  217. ErrorCode: ErrorCodeServerError,
  218. ErrorDescription: "cannot set code challenge",
  219. State: form.State,
  220. }, form.RedirectURI)
  221. return
  222. }
  223. // Here we're just going to try to release the session early
  224. if err := ctx.Session.Release(); err != nil {
  225. // we'll tolerate errors here as they *should* get saved elsewhere
  226. log.Error("Unable to save changes to the session: %v", err)
  227. }
  228. case "":
  229. // "Authorization servers SHOULD reject authorization requests from native apps that don't use PKCE by returning an error message"
  230. // https://datatracker.ietf.org/doc/html/rfc8252#section-8.1
  231. if !app.ConfidentialClient {
  232. // "the authorization endpoint MUST return the authorization error response with the "error" value set to "invalid_request""
  233. // https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1
  234. handleAuthorizeError(ctx, AuthorizeError{
  235. ErrorCode: ErrorCodeInvalidRequest,
  236. ErrorDescription: "PKCE is required for public clients",
  237. State: form.State,
  238. }, form.RedirectURI)
  239. return
  240. }
  241. default:
  242. // "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"."
  243. // https://www.rfc-editor.org/rfc/rfc7636#section-4.4.1
  244. handleAuthorizeError(ctx, AuthorizeError{
  245. ErrorCode: ErrorCodeInvalidRequest,
  246. ErrorDescription: "unsupported code challenge method",
  247. State: form.State,
  248. }, form.RedirectURI)
  249. return
  250. }
  251. grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID)
  252. if err != nil {
  253. handleServerError(ctx, form.State, form.RedirectURI)
  254. return
  255. }
  256. // Redirect if user already granted access and the application is confidential or trusted otherwise
  257. // I.e. always require authorization for untrusted public clients as recommended by RFC 6749 Section 10.2
  258. if (app.ConfidentialClient || app.SkipSecondaryAuthorization) && grant != nil {
  259. code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
  260. if err != nil {
  261. handleServerError(ctx, form.State, form.RedirectURI)
  262. return
  263. }
  264. redirect, err := code.GenerateRedirectURI(form.State)
  265. if err != nil {
  266. handleServerError(ctx, form.State, form.RedirectURI)
  267. return
  268. }
  269. // Update nonce to reflect the new session
  270. if len(form.Nonce) > 0 {
  271. err := grant.SetNonce(ctx, form.Nonce)
  272. if err != nil {
  273. log.Error("Unable to update nonce: %v", err)
  274. }
  275. }
  276. ctx.Redirect(redirect.String())
  277. return
  278. }
  279. // check if additional scopes
  280. ctx.Data["AdditionalScopes"] = oauth2_provider.GrantAdditionalScopes(form.Scope) != auth.AccessTokenScopeAll
  281. // show authorize page to grant access
  282. ctx.Data["Application"] = app
  283. ctx.Data["RedirectURI"] = form.RedirectURI
  284. ctx.Data["State"] = form.State
  285. ctx.Data["Scope"] = form.Scope
  286. ctx.Data["Nonce"] = form.Nonce
  287. if user != nil {
  288. ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">@%s</a>`, html.EscapeString(user.HomeLink()), html.EscapeString(user.Name)))
  289. } else {
  290. ctx.Data["ApplicationCreatorLinkHTML"] = template.HTML(fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(setting.AppSubURL+"/"), html.EscapeString(setting.AppName)))
  291. }
  292. ctx.Data["ApplicationRedirectDomainHTML"] = template.HTML("<strong>" + html.EscapeString(form.RedirectURI) + "</strong>")
  293. // TODO document SESSION <=> FORM
  294. err = ctx.Session.Set("client_id", app.ClientID)
  295. if err != nil {
  296. handleServerError(ctx, form.State, form.RedirectURI)
  297. log.Error(err.Error())
  298. return
  299. }
  300. err = ctx.Session.Set("redirect_uri", form.RedirectURI)
  301. if err != nil {
  302. handleServerError(ctx, form.State, form.RedirectURI)
  303. log.Error(err.Error())
  304. return
  305. }
  306. err = ctx.Session.Set("state", form.State)
  307. if err != nil {
  308. handleServerError(ctx, form.State, form.RedirectURI)
  309. log.Error(err.Error())
  310. return
  311. }
  312. // Here we're just going to try to release the session early
  313. if err := ctx.Session.Release(); err != nil {
  314. // we'll tolerate errors here as they *should* get saved elsewhere
  315. log.Error("Unable to save changes to the session: %v", err)
  316. }
  317. ctx.HTML(http.StatusOK, tplGrantAccess)
  318. }
  319. // GrantApplicationOAuth manages the post request submitted when a user grants access to an application
  320. func GrantApplicationOAuth(ctx *context.Context) {
  321. form := web.GetForm(ctx).(*forms.GrantApplicationForm)
  322. if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
  323. ctx.Session.Get("redirect_uri") != form.RedirectURI {
  324. ctx.HTTPError(http.StatusBadRequest)
  325. return
  326. }
  327. if !form.Granted {
  328. handleAuthorizeError(ctx, AuthorizeError{
  329. State: form.State,
  330. ErrorDescription: "the request is denied",
  331. ErrorCode: ErrorCodeAccessDenied,
  332. }, form.RedirectURI)
  333. return
  334. }
  335. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  336. if err != nil {
  337. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  338. return
  339. }
  340. grant, err := app.GetGrantByUserID(ctx, ctx.Doer.ID)
  341. if err != nil {
  342. handleServerError(ctx, form.State, form.RedirectURI)
  343. return
  344. }
  345. if grant == nil {
  346. grant, err = app.CreateGrant(ctx, ctx.Doer.ID, form.Scope)
  347. if err != nil {
  348. handleAuthorizeError(ctx, AuthorizeError{
  349. State: form.State,
  350. ErrorDescription: "cannot create grant for user",
  351. ErrorCode: ErrorCodeServerError,
  352. }, form.RedirectURI)
  353. return
  354. }
  355. } else if grant.Scope != form.Scope {
  356. handleAuthorizeError(ctx, AuthorizeError{
  357. State: form.State,
  358. ErrorDescription: "a grant exists with different scope",
  359. ErrorCode: ErrorCodeServerError,
  360. }, form.RedirectURI)
  361. return
  362. }
  363. if len(form.Nonce) > 0 {
  364. err := grant.SetNonce(ctx, form.Nonce)
  365. if err != nil {
  366. log.Error("Unable to update nonce: %v", err)
  367. }
  368. }
  369. var codeChallenge, codeChallengeMethod string
  370. codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
  371. codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
  372. code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, codeChallenge, codeChallengeMethod)
  373. if err != nil {
  374. handleServerError(ctx, form.State, form.RedirectURI)
  375. return
  376. }
  377. redirect, err := code.GenerateRedirectURI(form.State)
  378. if err != nil {
  379. handleServerError(ctx, form.State, form.RedirectURI)
  380. return
  381. }
  382. ctx.Redirect(redirect.String(), http.StatusSeeOther)
  383. }
  384. // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
  385. func OIDCWellKnown(ctx *context.Context) {
  386. if !setting.OAuth2.Enabled {
  387. http.NotFound(ctx.Resp, ctx.Req)
  388. return
  389. }
  390. jwtRegisteredClaims := oauth2_provider.NewJwtRegisteredClaimsFromUser("well-known", 0, nil)
  391. ctx.Data["OidcIssuer"] = jwtRegisteredClaims.Issuer // use the consistent issuer from the JWT registered claims
  392. ctx.Data["OidcBaseUrl"] = strings.TrimSuffix(setting.AppURL, "/")
  393. ctx.Data["SigningKeyMethodAlg"] = oauth2_provider.DefaultSigningKey.SigningMethod().Alg()
  394. ctx.JSONTemplate("user/auth/oidc_wellknown")
  395. }
  396. // OIDCKeys generates the JSON Web Key Set
  397. func OIDCKeys(ctx *context.Context) {
  398. jwk, err := oauth2_provider.DefaultSigningKey.ToJWK()
  399. if err != nil {
  400. log.Error("Error converting signing key to JWK: %v", err)
  401. ctx.HTTPError(http.StatusInternalServerError)
  402. return
  403. }
  404. jwk["use"] = "sig"
  405. jwks := map[string][]map[string]string{
  406. "keys": {
  407. jwk,
  408. },
  409. }
  410. ctx.Resp.Header().Set("Content-Type", "application/json")
  411. enc := json.NewEncoder(ctx.Resp)
  412. if err := enc.Encode(jwks); err != nil {
  413. log.Error("Failed to encode representation as json. Error: %v", err)
  414. }
  415. }
  416. // AccessTokenOAuth manages all access token requests by the client
  417. func AccessTokenOAuth(ctx *context.Context) {
  418. form := *web.GetForm(ctx).(*forms.AccessTokenForm)
  419. // 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
  420. if form.ClientID == "" || form.ClientSecret == "" {
  421. if authHeader := ctx.Req.Header.Get("Authorization"); authHeader != "" {
  422. parsed, ok := httpauth.ParseAuthorizationHeader(authHeader)
  423. if !ok || parsed.BasicAuth == nil {
  424. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  425. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
  426. ErrorDescription: "cannot parse basic auth header",
  427. })
  428. return
  429. }
  430. clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password
  431. // validate that any fields present in the form match the Basic auth header
  432. if form.ClientID != "" && form.ClientID != clientID {
  433. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  434. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
  435. ErrorDescription: "client_id in request body inconsistent with Authorization header",
  436. })
  437. return
  438. }
  439. form.ClientID = clientID
  440. if form.ClientSecret != "" && form.ClientSecret != clientSecret {
  441. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  442. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
  443. ErrorDescription: "client_secret in request body inconsistent with Authorization header",
  444. })
  445. return
  446. }
  447. form.ClientSecret = clientSecret
  448. }
  449. }
  450. serverKey := oauth2_provider.DefaultSigningKey
  451. clientKey := serverKey
  452. if serverKey.IsSymmetric() {
  453. var err error
  454. clientKey, err = oauth2_provider.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret))
  455. if err != nil {
  456. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  457. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
  458. ErrorDescription: "Error creating signing key",
  459. })
  460. return
  461. }
  462. }
  463. switch form.GrantType {
  464. case "refresh_token":
  465. handleRefreshToken(ctx, form, serverKey, clientKey)
  466. case "authorization_code":
  467. handleAuthorizationCode(ctx, form, serverKey, clientKey)
  468. default:
  469. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  470. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnsupportedGrantType,
  471. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  472. })
  473. }
  474. }
  475. func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2_provider.JWTSigningKey) {
  476. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  477. if err != nil {
  478. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  479. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidClient,
  480. ErrorDescription: fmt.Sprintf("cannot load client with client id: %q", form.ClientID),
  481. })
  482. return
  483. }
  484. // "The authorization server MUST ... require client authentication for confidential clients"
  485. // https://datatracker.ietf.org/doc/html/rfc6749#section-6
  486. if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  487. errorDescription := "invalid client secret"
  488. if form.ClientSecret == "" {
  489. errorDescription = "invalid empty client secret"
  490. }
  491. // "invalid_client ... Client authentication failed"
  492. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  493. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  494. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidClient,
  495. ErrorDescription: errorDescription,
  496. })
  497. return
  498. }
  499. token, err := oauth2_provider.ParseToken(form.RefreshToken, serverKey)
  500. if err != nil {
  501. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  502. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient,
  503. ErrorDescription: "unable to parse refresh token",
  504. })
  505. return
  506. }
  507. // get grant before increasing counter
  508. grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID)
  509. if err != nil || grant == nil {
  510. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  511. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidGrant,
  512. ErrorDescription: "grant does not exist",
  513. })
  514. return
  515. }
  516. // check if token got already used
  517. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  518. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  519. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient,
  520. ErrorDescription: "token was already used",
  521. })
  522. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  523. return
  524. }
  525. accessToken, tokenErr := oauth2_provider.NewAccessTokenResponse(ctx, grant, serverKey, clientKey)
  526. if tokenErr != nil {
  527. handleAccessTokenError(ctx, *tokenErr)
  528. return
  529. }
  530. ctx.JSON(http.StatusOK, accessToken)
  531. }
  532. func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2_provider.JWTSigningKey) {
  533. app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
  534. if err != nil {
  535. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  536. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidClient,
  537. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  538. })
  539. return
  540. }
  541. if app.ConfidentialClient && !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  542. errorDescription := "invalid client secret"
  543. if form.ClientSecret == "" {
  544. errorDescription = "invalid empty client secret"
  545. }
  546. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  547. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient,
  548. ErrorDescription: errorDescription,
  549. })
  550. return
  551. }
  552. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  553. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  554. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient,
  555. ErrorDescription: "unexpected redirect URI",
  556. })
  557. return
  558. }
  559. authorizationCode, err := auth.GetOAuth2AuthorizationByCode(ctx, form.Code)
  560. if err != nil || authorizationCode == nil {
  561. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  562. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient,
  563. ErrorDescription: "client is not authorized",
  564. })
  565. return
  566. }
  567. // check if code verifier authorizes the client, PKCE support
  568. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  569. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  570. ErrorCode: oauth2_provider.AccessTokenErrorCodeUnauthorizedClient,
  571. ErrorDescription: "failed PKCE code challenge",
  572. })
  573. return
  574. }
  575. // check if granted for this application
  576. if authorizationCode.Grant.ApplicationID != app.ID {
  577. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  578. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidGrant,
  579. ErrorDescription: "invalid grant",
  580. })
  581. return
  582. }
  583. // remove token from database to deny duplicate usage
  584. if err := authorizationCode.Invalidate(ctx); err != nil {
  585. handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{
  586. ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest,
  587. ErrorDescription: "cannot proceed your request",
  588. })
  589. }
  590. resp, tokenErr := oauth2_provider.NewAccessTokenResponse(ctx, authorizationCode.Grant, serverKey, clientKey)
  591. if tokenErr != nil {
  592. handleAccessTokenError(ctx, *tokenErr)
  593. return
  594. }
  595. // send successful response
  596. ctx.JSON(http.StatusOK, resp)
  597. }
  598. func handleAccessTokenError(ctx *context.Context, acErr oauth2_provider.AccessTokenError) {
  599. ctx.JSON(http.StatusBadRequest, acErr)
  600. }
  601. func handleServerError(ctx *context.Context, state, redirectURI string) {
  602. handleAuthorizeError(ctx, AuthorizeError{
  603. ErrorCode: ErrorCodeServerError,
  604. ErrorDescription: "A server error occurred",
  605. State: state,
  606. }, redirectURI)
  607. }
  608. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  609. if redirectURI == "" {
  610. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  611. ctx.Data["Error"] = authErr
  612. ctx.HTML(http.StatusBadRequest, tplGrantError)
  613. return
  614. }
  615. redirect, err := url.Parse(redirectURI)
  616. if err != nil {
  617. ctx.ServerError("url.Parse", err)
  618. return
  619. }
  620. q := redirect.Query()
  621. q.Set("error", string(authErr.ErrorCode))
  622. q.Set("error_description", authErr.ErrorDescription)
  623. q.Set("state", authErr.State)
  624. redirect.RawQuery = q.Encode()
  625. ctx.Redirect(redirect.String(), http.StatusSeeOther)
  626. }