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

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