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.

479 lines
16 KiB

  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. "github.com/dgrijalva/jwt-go"
  11. "github.com/go-macaron/binding"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/modules/auth"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/util"
  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 := util.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 := util.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. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  147. if err != nil {
  148. if models.IsErrOauthClientIDInvalid(err) {
  149. handleAuthorizeError(ctx, AuthorizeError{
  150. ErrorCode: ErrorCodeUnauthorizedClient,
  151. ErrorDescription: "Client ID not registered",
  152. State: form.State,
  153. }, "")
  154. return
  155. }
  156. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  157. return
  158. }
  159. if err := app.LoadUser(); err != nil {
  160. ctx.ServerError("LoadUser", err)
  161. return
  162. }
  163. if !app.ContainsRedirectURI(form.RedirectURI) {
  164. handleAuthorizeError(ctx, AuthorizeError{
  165. ErrorCode: ErrorCodeInvalidRequest,
  166. ErrorDescription: "Unregistered Redirect URI",
  167. State: form.State,
  168. }, "")
  169. return
  170. }
  171. if form.ResponseType != "code" {
  172. handleAuthorizeError(ctx, AuthorizeError{
  173. ErrorCode: ErrorCodeUnsupportedResponseType,
  174. ErrorDescription: "Only code response type is supported.",
  175. State: form.State,
  176. }, form.RedirectURI)
  177. return
  178. }
  179. // pkce support
  180. switch form.CodeChallengeMethod {
  181. case "S256":
  182. case "plain":
  183. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
  184. handleAuthorizeError(ctx, AuthorizeError{
  185. ErrorCode: ErrorCodeServerError,
  186. ErrorDescription: "cannot set code challenge method",
  187. State: form.State,
  188. }, form.RedirectURI)
  189. return
  190. }
  191. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
  192. handleAuthorizeError(ctx, AuthorizeError{
  193. ErrorCode: ErrorCodeServerError,
  194. ErrorDescription: "cannot set code challenge",
  195. State: form.State,
  196. }, form.RedirectURI)
  197. return
  198. }
  199. break
  200. case "":
  201. break
  202. default:
  203. handleAuthorizeError(ctx, AuthorizeError{
  204. ErrorCode: ErrorCodeInvalidRequest,
  205. ErrorDescription: "unsupported code challenge method",
  206. State: form.State,
  207. }, form.RedirectURI)
  208. return
  209. }
  210. grant, err := app.GetGrantByUserID(ctx.User.ID)
  211. if err != nil {
  212. handleServerError(ctx, form.State, form.RedirectURI)
  213. return
  214. }
  215. // Redirect if user already granted access
  216. if grant != nil {
  217. code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
  218. if err != nil {
  219. handleServerError(ctx, form.State, form.RedirectURI)
  220. return
  221. }
  222. redirect, err := code.GenerateRedirectURI(form.State)
  223. if err != nil {
  224. handleServerError(ctx, form.State, form.RedirectURI)
  225. return
  226. }
  227. ctx.Redirect(redirect.String(), 302)
  228. return
  229. }
  230. // show authorize page to grant access
  231. ctx.Data["Application"] = app
  232. ctx.Data["RedirectURI"] = form.RedirectURI
  233. ctx.Data["State"] = form.State
  234. ctx.Data["ApplicationUserLink"] = "<a href=\"" + setting.LocalURL + app.User.LowerName + "\">@" + app.User.Name + "</a>"
  235. ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + form.RedirectURI + "</strong>"
  236. // TODO document SESSION <=> FORM
  237. ctx.Session.Set("client_id", app.ClientID)
  238. ctx.Session.Set("redirect_uri", form.RedirectURI)
  239. ctx.Session.Set("state", form.State)
  240. ctx.HTML(200, tplGrantAccess)
  241. }
  242. // GrantApplicationOAuth manages the post request submitted when a user grants access to an application
  243. func GrantApplicationOAuth(ctx *context.Context, form auth.GrantApplicationForm) {
  244. if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
  245. ctx.Session.Get("redirect_uri") != form.RedirectURI {
  246. ctx.Error(400)
  247. return
  248. }
  249. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  250. if err != nil {
  251. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  252. return
  253. }
  254. grant, err := app.CreateGrant(ctx.User.ID)
  255. if err != nil {
  256. handleAuthorizeError(ctx, AuthorizeError{
  257. State: form.State,
  258. ErrorDescription: "cannot create grant for user",
  259. ErrorCode: ErrorCodeServerError,
  260. }, form.RedirectURI)
  261. return
  262. }
  263. var codeChallenge, codeChallengeMethod string
  264. codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
  265. codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
  266. code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, codeChallenge, codeChallengeMethod)
  267. if err != nil {
  268. handleServerError(ctx, form.State, form.RedirectURI)
  269. return
  270. }
  271. redirect, err := code.GenerateRedirectURI(form.State)
  272. if err != nil {
  273. handleServerError(ctx, form.State, form.RedirectURI)
  274. }
  275. ctx.Redirect(redirect.String(), 302)
  276. }
  277. // AccessTokenOAuth manages all access token requests by the client
  278. func AccessTokenOAuth(ctx *context.Context, form auth.AccessTokenForm) {
  279. if form.ClientID == "" {
  280. authHeader := ctx.Req.Header.Get("Authorization")
  281. authContent := strings.SplitN(authHeader, " ", 2)
  282. if len(authContent) == 2 && authContent[0] == "Basic" {
  283. payload, err := base64.StdEncoding.DecodeString(authContent[1])
  284. if err != nil {
  285. handleAccessTokenError(ctx, AccessTokenError{
  286. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  287. ErrorDescription: "cannot parse basic auth header",
  288. })
  289. return
  290. }
  291. pair := strings.SplitN(string(payload), ":", 2)
  292. if len(pair) != 2 {
  293. handleAccessTokenError(ctx, AccessTokenError{
  294. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  295. ErrorDescription: "cannot parse basic auth header",
  296. })
  297. return
  298. }
  299. form.ClientID = pair[0]
  300. form.ClientSecret = pair[1]
  301. }
  302. }
  303. switch form.GrantType {
  304. case "refresh_token":
  305. handleRefreshToken(ctx, form)
  306. return
  307. case "authorization_code":
  308. handleAuthorizationCode(ctx, form)
  309. return
  310. default:
  311. handleAccessTokenError(ctx, AccessTokenError{
  312. ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
  313. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  314. })
  315. }
  316. }
  317. func handleRefreshToken(ctx *context.Context, form auth.AccessTokenForm) {
  318. token, err := models.ParseOAuth2Token(form.RefreshToken)
  319. if err != nil {
  320. handleAccessTokenError(ctx, AccessTokenError{
  321. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  322. ErrorDescription: "client is not authorized",
  323. })
  324. return
  325. }
  326. // get grant before increasing counter
  327. grant, err := models.GetOAuth2GrantByID(token.GrantID)
  328. if err != nil || grant == nil {
  329. handleAccessTokenError(ctx, AccessTokenError{
  330. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  331. ErrorDescription: "grant does not exist",
  332. })
  333. return
  334. }
  335. // check if token got already used
  336. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  337. handleAccessTokenError(ctx, AccessTokenError{
  338. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  339. ErrorDescription: "token was already used",
  340. })
  341. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  342. return
  343. }
  344. accessToken, tokenErr := newAccessTokenResponse(grant)
  345. if tokenErr != nil {
  346. handleAccessTokenError(ctx, *tokenErr)
  347. return
  348. }
  349. ctx.JSON(200, accessToken)
  350. }
  351. func handleAuthorizationCode(ctx *context.Context, form auth.AccessTokenForm) {
  352. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  353. if err != nil {
  354. handleAccessTokenError(ctx, AccessTokenError{
  355. ErrorCode: AccessTokenErrorCodeInvalidClient,
  356. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  357. })
  358. return
  359. }
  360. if !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  361. handleAccessTokenError(ctx, AccessTokenError{
  362. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  363. ErrorDescription: "client is not authorized",
  364. })
  365. return
  366. }
  367. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  368. handleAccessTokenError(ctx, AccessTokenError{
  369. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  370. ErrorDescription: "client is not authorized",
  371. })
  372. return
  373. }
  374. authorizationCode, err := models.GetOAuth2AuthorizationByCode(form.Code)
  375. if err != nil || authorizationCode == nil {
  376. handleAccessTokenError(ctx, AccessTokenError{
  377. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  378. ErrorDescription: "client is not authorized",
  379. })
  380. return
  381. }
  382. // check if code verifier authorizes the client, PKCE support
  383. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  384. handleAccessTokenError(ctx, AccessTokenError{
  385. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  386. ErrorDescription: "client is not authorized",
  387. })
  388. return
  389. }
  390. // check if granted for this application
  391. if authorizationCode.Grant.ApplicationID != app.ID {
  392. handleAccessTokenError(ctx, AccessTokenError{
  393. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  394. ErrorDescription: "invalid grant",
  395. })
  396. return
  397. }
  398. // remove token from database to deny duplicate usage
  399. if err := authorizationCode.Invalidate(); err != nil {
  400. handleAccessTokenError(ctx, AccessTokenError{
  401. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  402. ErrorDescription: "cannot proceed your request",
  403. })
  404. }
  405. resp, tokenErr := newAccessTokenResponse(authorizationCode.Grant)
  406. if tokenErr != nil {
  407. handleAccessTokenError(ctx, *tokenErr)
  408. return
  409. }
  410. // send successful response
  411. ctx.JSON(200, resp)
  412. }
  413. func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
  414. ctx.JSON(400, acErr)
  415. }
  416. func handleServerError(ctx *context.Context, state string, redirectURI string) {
  417. handleAuthorizeError(ctx, AuthorizeError{
  418. ErrorCode: ErrorCodeServerError,
  419. ErrorDescription: "A server error occurred",
  420. State: state,
  421. }, redirectURI)
  422. }
  423. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  424. if redirectURI == "" {
  425. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  426. ctx.Data["Error"] = authErr
  427. ctx.HTML(400, tplGrantError)
  428. return
  429. }
  430. redirect, err := url.Parse(redirectURI)
  431. if err != nil {
  432. ctx.ServerError("url.Parse", err)
  433. return
  434. }
  435. q := redirect.Query()
  436. q.Set("error", string(authErr.ErrorCode))
  437. q.Set("error_description", authErr.ErrorDescription)
  438. q.Set("state", authErr.State)
  439. redirect.RawQuery = q.Encode()
  440. ctx.Redirect(redirect.String(), 302)
  441. }