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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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 user
  5. import (
  6. "encoding/base64"
  7. "fmt"
  8. "net/url"
  9. "strings"
  10. "code.gitea.io/gitea/models"
  11. "code.gitea.io/gitea/modules/auth"
  12. "code.gitea.io/gitea/modules/base"
  13. "code.gitea.io/gitea/modules/context"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/timeutil"
  17. "gitea.com/macaron/binding"
  18. "github.com/dgrijalva/jwt-go"
  19. )
  20. const (
  21. tplGrantAccess base.TplName = "user/auth/grant"
  22. tplGrantError base.TplName = "user/auth/grant_error"
  23. )
  24. // TODO move error and responses to SDK or models
  25. // AuthorizeErrorCode represents an error code specified in RFC 6749
  26. type AuthorizeErrorCode string
  27. const (
  28. // ErrorCodeInvalidRequest represents the according error in RFC 6749
  29. ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request"
  30. // ErrorCodeUnauthorizedClient represents the according error in RFC 6749
  31. ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client"
  32. // ErrorCodeAccessDenied represents the according error in RFC 6749
  33. ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied"
  34. // ErrorCodeUnsupportedResponseType represents the according error in RFC 6749
  35. ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type"
  36. // ErrorCodeInvalidScope represents the according error in RFC 6749
  37. ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope"
  38. // ErrorCodeServerError represents the according error in RFC 6749
  39. ErrorCodeServerError AuthorizeErrorCode = "server_error"
  40. // ErrorCodeTemporaryUnavailable represents the according error in RFC 6749
  41. ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable"
  42. )
  43. // AuthorizeError represents an error type specified in RFC 6749
  44. type AuthorizeError struct {
  45. ErrorCode AuthorizeErrorCode `json:"error" form:"error"`
  46. ErrorDescription string
  47. State string
  48. }
  49. // Error returns the error message
  50. func (err AuthorizeError) Error() string {
  51. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  52. }
  53. // AccessTokenErrorCode represents an error code specified in RFC 6749
  54. type AccessTokenErrorCode string
  55. const (
  56. // AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749
  57. AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request"
  58. // AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749
  59. AccessTokenErrorCodeInvalidClient = "invalid_client"
  60. // AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749
  61. AccessTokenErrorCodeInvalidGrant = "invalid_grant"
  62. // AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749
  63. AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client"
  64. // AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749
  65. AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type"
  66. // AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749
  67. AccessTokenErrorCodeInvalidScope = "invalid_scope"
  68. )
  69. // AccessTokenError represents an error response specified in RFC 6749
  70. type AccessTokenError struct {
  71. ErrorCode AccessTokenErrorCode `json:"error" form:"error"`
  72. ErrorDescription string `json:"error_description"`
  73. }
  74. // Error returns the error message
  75. func (err AccessTokenError) Error() string {
  76. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  77. }
  78. // TokenType specifies the kind of token
  79. type TokenType string
  80. const (
  81. // TokenTypeBearer represents a token type specified in RFC 6749
  82. TokenTypeBearer TokenType = "bearer"
  83. // TokenTypeMAC represents a token type specified in RFC 6749
  84. TokenTypeMAC = "mac"
  85. )
  86. // AccessTokenResponse represents a successful access token response
  87. type AccessTokenResponse struct {
  88. AccessToken string `json:"access_token"`
  89. TokenType TokenType `json:"token_type"`
  90. ExpiresIn int64 `json:"expires_in"`
  91. RefreshToken string `json:"refresh_token"`
  92. }
  93. func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *AccessTokenError) {
  94. if setting.OAuth2.InvalidateRefreshTokens {
  95. if err := grant.IncreaseCounter(); err != nil {
  96. return nil, &AccessTokenError{
  97. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  98. ErrorDescription: "cannot increase the grant counter",
  99. }
  100. }
  101. }
  102. // generate access token to access the API
  103. expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
  104. accessToken := &models.OAuth2Token{
  105. GrantID: grant.ID,
  106. Type: models.TypeAccessToken,
  107. StandardClaims: jwt.StandardClaims{
  108. ExpiresAt: expirationDate.AsTime().Unix(),
  109. },
  110. }
  111. signedAccessToken, err := accessToken.SignToken()
  112. if err != nil {
  113. return nil, &AccessTokenError{
  114. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  115. ErrorDescription: "cannot sign token",
  116. }
  117. }
  118. // generate refresh token to request an access token after it expired later
  119. refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime().Unix()
  120. refreshToken := &models.OAuth2Token{
  121. GrantID: grant.ID,
  122. Counter: grant.Counter,
  123. Type: models.TypeRefreshToken,
  124. StandardClaims: jwt.StandardClaims{
  125. ExpiresAt: refreshExpirationDate,
  126. },
  127. }
  128. signedRefreshToken, err := refreshToken.SignToken()
  129. if err != nil {
  130. return nil, &AccessTokenError{
  131. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  132. ErrorDescription: "cannot sign token",
  133. }
  134. }
  135. return &AccessTokenResponse{
  136. AccessToken: signedAccessToken,
  137. TokenType: TokenTypeBearer,
  138. ExpiresIn: setting.OAuth2.AccessTokenExpirationTime,
  139. RefreshToken: signedRefreshToken,
  140. }, nil
  141. }
  142. // AuthorizeOAuth manages authorize requests
  143. func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
  144. errs := binding.Errors{}
  145. errs = form.Validate(ctx.Context, errs)
  146. if len(errs) > 0 {
  147. errstring := ""
  148. for _, e := range errs {
  149. errstring += e.Error() + "\n"
  150. }
  151. ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
  152. return
  153. }
  154. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  155. if err != nil {
  156. if models.IsErrOauthClientIDInvalid(err) {
  157. handleAuthorizeError(ctx, AuthorizeError{
  158. ErrorCode: ErrorCodeUnauthorizedClient,
  159. ErrorDescription: "Client ID not registered",
  160. State: form.State,
  161. }, "")
  162. return
  163. }
  164. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  165. return
  166. }
  167. if err := app.LoadUser(); err != nil {
  168. ctx.ServerError("LoadUser", err)
  169. return
  170. }
  171. if !app.ContainsRedirectURI(form.RedirectURI) {
  172. handleAuthorizeError(ctx, AuthorizeError{
  173. ErrorCode: ErrorCodeInvalidRequest,
  174. ErrorDescription: "Unregistered Redirect URI",
  175. State: form.State,
  176. }, "")
  177. return
  178. }
  179. if form.ResponseType != "code" {
  180. handleAuthorizeError(ctx, AuthorizeError{
  181. ErrorCode: ErrorCodeUnsupportedResponseType,
  182. ErrorDescription: "Only code response type is supported.",
  183. State: form.State,
  184. }, form.RedirectURI)
  185. return
  186. }
  187. // pkce support
  188. switch form.CodeChallengeMethod {
  189. case "S256":
  190. case "plain":
  191. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
  192. handleAuthorizeError(ctx, AuthorizeError{
  193. ErrorCode: ErrorCodeServerError,
  194. ErrorDescription: "cannot set code challenge method",
  195. State: form.State,
  196. }, form.RedirectURI)
  197. return
  198. }
  199. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
  200. handleAuthorizeError(ctx, AuthorizeError{
  201. ErrorCode: ErrorCodeServerError,
  202. ErrorDescription: "cannot set code challenge",
  203. State: form.State,
  204. }, form.RedirectURI)
  205. return
  206. }
  207. // Here we're just going to try to release the session early
  208. if err := ctx.Session.Release(); err != nil {
  209. // we'll tolerate errors here as they *should* get saved elsewhere
  210. log.Error("Unable to save changes to the session: %v", err)
  211. }
  212. case "":
  213. break
  214. default:
  215. handleAuthorizeError(ctx, AuthorizeError{
  216. ErrorCode: ErrorCodeInvalidRequest,
  217. ErrorDescription: "unsupported code challenge method",
  218. State: form.State,
  219. }, form.RedirectURI)
  220. return
  221. }
  222. grant, err := app.GetGrantByUserID(ctx.User.ID)
  223. if err != nil {
  224. handleServerError(ctx, form.State, form.RedirectURI)
  225. return
  226. }
  227. // Redirect if user already granted access
  228. if grant != nil {
  229. code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
  230. if err != nil {
  231. handleServerError(ctx, form.State, form.RedirectURI)
  232. return
  233. }
  234. redirect, err := code.GenerateRedirectURI(form.State)
  235. if err != nil {
  236. handleServerError(ctx, form.State, form.RedirectURI)
  237. return
  238. }
  239. ctx.Redirect(redirect.String(), 302)
  240. return
  241. }
  242. // show authorize page to grant access
  243. ctx.Data["Application"] = app
  244. ctx.Data["RedirectURI"] = form.RedirectURI
  245. ctx.Data["State"] = form.State
  246. ctx.Data["ApplicationUserLink"] = "<a href=\"" + setting.AppURL + app.User.LowerName + "\">@" + app.User.Name + "</a>"
  247. ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + form.RedirectURI + "</strong>"
  248. // TODO document SESSION <=> FORM
  249. err = ctx.Session.Set("client_id", app.ClientID)
  250. if err != nil {
  251. handleServerError(ctx, form.State, form.RedirectURI)
  252. log.Error(err.Error())
  253. return
  254. }
  255. err = ctx.Session.Set("redirect_uri", form.RedirectURI)
  256. if err != nil {
  257. handleServerError(ctx, form.State, form.RedirectURI)
  258. log.Error(err.Error())
  259. return
  260. }
  261. err = ctx.Session.Set("state", form.State)
  262. if err != nil {
  263. handleServerError(ctx, form.State, form.RedirectURI)
  264. log.Error(err.Error())
  265. return
  266. }
  267. // Here we're just going to try to release the session early
  268. if err := ctx.Session.Release(); err != nil {
  269. // we'll tolerate errors here as they *should* get saved elsewhere
  270. log.Error("Unable to save changes to the session: %v", err)
  271. }
  272. ctx.HTML(200, tplGrantAccess)
  273. }
  274. // GrantApplicationOAuth manages the post request submitted when a user grants access to an application
  275. func GrantApplicationOAuth(ctx *context.Context, form auth.GrantApplicationForm) {
  276. if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
  277. ctx.Session.Get("redirect_uri") != form.RedirectURI {
  278. ctx.Error(400)
  279. return
  280. }
  281. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  282. if err != nil {
  283. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  284. return
  285. }
  286. grant, err := app.CreateGrant(ctx.User.ID)
  287. if err != nil {
  288. handleAuthorizeError(ctx, AuthorizeError{
  289. State: form.State,
  290. ErrorDescription: "cannot create grant for user",
  291. ErrorCode: ErrorCodeServerError,
  292. }, form.RedirectURI)
  293. return
  294. }
  295. var codeChallenge, codeChallengeMethod string
  296. codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
  297. codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
  298. code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, codeChallenge, codeChallengeMethod)
  299. if err != nil {
  300. handleServerError(ctx, form.State, form.RedirectURI)
  301. return
  302. }
  303. redirect, err := code.GenerateRedirectURI(form.State)
  304. if err != nil {
  305. handleServerError(ctx, form.State, form.RedirectURI)
  306. return
  307. }
  308. ctx.Redirect(redirect.String(), 302)
  309. }
  310. // AccessTokenOAuth manages all access token requests by the client
  311. func AccessTokenOAuth(ctx *context.Context, form auth.AccessTokenForm) {
  312. if form.ClientID == "" {
  313. authHeader := ctx.Req.Header.Get("Authorization")
  314. authContent := strings.SplitN(authHeader, " ", 2)
  315. if len(authContent) == 2 && authContent[0] == "Basic" {
  316. payload, err := base64.StdEncoding.DecodeString(authContent[1])
  317. if err != nil {
  318. handleAccessTokenError(ctx, AccessTokenError{
  319. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  320. ErrorDescription: "cannot parse basic auth header",
  321. })
  322. return
  323. }
  324. pair := strings.SplitN(string(payload), ":", 2)
  325. if len(pair) != 2 {
  326. handleAccessTokenError(ctx, AccessTokenError{
  327. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  328. ErrorDescription: "cannot parse basic auth header",
  329. })
  330. return
  331. }
  332. form.ClientID = pair[0]
  333. form.ClientSecret = pair[1]
  334. }
  335. }
  336. switch form.GrantType {
  337. case "refresh_token":
  338. handleRefreshToken(ctx, form)
  339. return
  340. case "authorization_code":
  341. handleAuthorizationCode(ctx, form)
  342. return
  343. default:
  344. handleAccessTokenError(ctx, AccessTokenError{
  345. ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
  346. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  347. })
  348. }
  349. }
  350. func handleRefreshToken(ctx *context.Context, form auth.AccessTokenForm) {
  351. token, err := models.ParseOAuth2Token(form.RefreshToken)
  352. if err != nil {
  353. handleAccessTokenError(ctx, AccessTokenError{
  354. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  355. ErrorDescription: "client is not authorized",
  356. })
  357. return
  358. }
  359. // get grant before increasing counter
  360. grant, err := models.GetOAuth2GrantByID(token.GrantID)
  361. if err != nil || grant == nil {
  362. handleAccessTokenError(ctx, AccessTokenError{
  363. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  364. ErrorDescription: "grant does not exist",
  365. })
  366. return
  367. }
  368. // check if token got already used
  369. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  370. handleAccessTokenError(ctx, AccessTokenError{
  371. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  372. ErrorDescription: "token was already used",
  373. })
  374. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  375. return
  376. }
  377. accessToken, tokenErr := newAccessTokenResponse(grant)
  378. if tokenErr != nil {
  379. handleAccessTokenError(ctx, *tokenErr)
  380. return
  381. }
  382. ctx.JSON(200, accessToken)
  383. }
  384. func handleAuthorizationCode(ctx *context.Context, form auth.AccessTokenForm) {
  385. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  386. if err != nil {
  387. handleAccessTokenError(ctx, AccessTokenError{
  388. ErrorCode: AccessTokenErrorCodeInvalidClient,
  389. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  390. })
  391. return
  392. }
  393. if !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  394. handleAccessTokenError(ctx, AccessTokenError{
  395. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  396. ErrorDescription: "client is not authorized",
  397. })
  398. return
  399. }
  400. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  401. handleAccessTokenError(ctx, AccessTokenError{
  402. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  403. ErrorDescription: "client is not authorized",
  404. })
  405. return
  406. }
  407. authorizationCode, err := models.GetOAuth2AuthorizationByCode(form.Code)
  408. if err != nil || authorizationCode == nil {
  409. handleAccessTokenError(ctx, AccessTokenError{
  410. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  411. ErrorDescription: "client is not authorized",
  412. })
  413. return
  414. }
  415. // check if code verifier authorizes the client, PKCE support
  416. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  417. handleAccessTokenError(ctx, AccessTokenError{
  418. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  419. ErrorDescription: "client is not authorized",
  420. })
  421. return
  422. }
  423. // check if granted for this application
  424. if authorizationCode.Grant.ApplicationID != app.ID {
  425. handleAccessTokenError(ctx, AccessTokenError{
  426. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  427. ErrorDescription: "invalid grant",
  428. })
  429. return
  430. }
  431. // remove token from database to deny duplicate usage
  432. if err := authorizationCode.Invalidate(); err != nil {
  433. handleAccessTokenError(ctx, AccessTokenError{
  434. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  435. ErrorDescription: "cannot proceed your request",
  436. })
  437. }
  438. resp, tokenErr := newAccessTokenResponse(authorizationCode.Grant)
  439. if tokenErr != nil {
  440. handleAccessTokenError(ctx, *tokenErr)
  441. return
  442. }
  443. // send successful response
  444. ctx.JSON(200, resp)
  445. }
  446. func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
  447. ctx.JSON(400, acErr)
  448. }
  449. func handleServerError(ctx *context.Context, state string, redirectURI string) {
  450. handleAuthorizeError(ctx, AuthorizeError{
  451. ErrorCode: ErrorCodeServerError,
  452. ErrorDescription: "A server error occurred",
  453. State: state,
  454. }, redirectURI)
  455. }
  456. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  457. if redirectURI == "" {
  458. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  459. ctx.Data["Error"] = authErr
  460. ctx.HTML(400, tplGrantError)
  461. return
  462. }
  463. redirect, err := url.Parse(redirectURI)
  464. if err != nil {
  465. ctx.ServerError("url.Parse", err)
  466. return
  467. }
  468. q := redirect.Query()
  469. q.Set("error", string(authErr.ErrorCode))
  470. q.Set("error_description", authErr.ErrorDescription)
  471. q.Set("state", authErr.State)
  472. redirect.RawQuery = q.Encode()
  473. ctx.Redirect(redirect.String(), 302)
  474. }