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.

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