You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

oauth.go 16 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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.AppURL + 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. return
  275. }
  276. ctx.Redirect(redirect.String(), 302)
  277. }
  278. // AccessTokenOAuth manages all access token requests by the client
  279. func AccessTokenOAuth(ctx *context.Context, form auth.AccessTokenForm) {
  280. if form.ClientID == "" {
  281. authHeader := ctx.Req.Header.Get("Authorization")
  282. authContent := strings.SplitN(authHeader, " ", 2)
  283. if len(authContent) == 2 && authContent[0] == "Basic" {
  284. payload, err := base64.StdEncoding.DecodeString(authContent[1])
  285. if err != nil {
  286. handleAccessTokenError(ctx, AccessTokenError{
  287. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  288. ErrorDescription: "cannot parse basic auth header",
  289. })
  290. return
  291. }
  292. pair := strings.SplitN(string(payload), ":", 2)
  293. if len(pair) != 2 {
  294. handleAccessTokenError(ctx, AccessTokenError{
  295. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  296. ErrorDescription: "cannot parse basic auth header",
  297. })
  298. return
  299. }
  300. form.ClientID = pair[0]
  301. form.ClientSecret = pair[1]
  302. }
  303. }
  304. switch form.GrantType {
  305. case "refresh_token":
  306. handleRefreshToken(ctx, form)
  307. return
  308. case "authorization_code":
  309. handleAuthorizationCode(ctx, form)
  310. return
  311. default:
  312. handleAccessTokenError(ctx, AccessTokenError{
  313. ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
  314. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  315. })
  316. }
  317. }
  318. func handleRefreshToken(ctx *context.Context, form auth.AccessTokenForm) {
  319. token, err := models.ParseOAuth2Token(form.RefreshToken)
  320. if err != nil {
  321. handleAccessTokenError(ctx, AccessTokenError{
  322. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  323. ErrorDescription: "client is not authorized",
  324. })
  325. return
  326. }
  327. // get grant before increasing counter
  328. grant, err := models.GetOAuth2GrantByID(token.GrantID)
  329. if err != nil || grant == nil {
  330. handleAccessTokenError(ctx, AccessTokenError{
  331. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  332. ErrorDescription: "grant does not exist",
  333. })
  334. return
  335. }
  336. // check if token got already used
  337. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  338. handleAccessTokenError(ctx, AccessTokenError{
  339. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  340. ErrorDescription: "token was already used",
  341. })
  342. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  343. return
  344. }
  345. accessToken, tokenErr := newAccessTokenResponse(grant)
  346. if tokenErr != nil {
  347. handleAccessTokenError(ctx, *tokenErr)
  348. return
  349. }
  350. ctx.JSON(200, accessToken)
  351. }
  352. func handleAuthorizationCode(ctx *context.Context, form auth.AccessTokenForm) {
  353. app, err := models.GetOAuth2ApplicationByClientID(form.ClientID)
  354. if err != nil {
  355. handleAccessTokenError(ctx, AccessTokenError{
  356. ErrorCode: AccessTokenErrorCodeInvalidClient,
  357. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  358. })
  359. return
  360. }
  361. if !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  362. handleAccessTokenError(ctx, AccessTokenError{
  363. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  364. ErrorDescription: "client is not authorized",
  365. })
  366. return
  367. }
  368. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  369. handleAccessTokenError(ctx, AccessTokenError{
  370. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  371. ErrorDescription: "client is not authorized",
  372. })
  373. return
  374. }
  375. authorizationCode, err := models.GetOAuth2AuthorizationByCode(form.Code)
  376. if err != nil || authorizationCode == nil {
  377. handleAccessTokenError(ctx, AccessTokenError{
  378. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  379. ErrorDescription: "client is not authorized",
  380. })
  381. return
  382. }
  383. // check if code verifier authorizes the client, PKCE support
  384. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  385. handleAccessTokenError(ctx, AccessTokenError{
  386. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  387. ErrorDescription: "client is not authorized",
  388. })
  389. return
  390. }
  391. // check if granted for this application
  392. if authorizationCode.Grant.ApplicationID != app.ID {
  393. handleAccessTokenError(ctx, AccessTokenError{
  394. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  395. ErrorDescription: "invalid grant",
  396. })
  397. return
  398. }
  399. // remove token from database to deny duplicate usage
  400. if err := authorizationCode.Invalidate(); err != nil {
  401. handleAccessTokenError(ctx, AccessTokenError{
  402. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  403. ErrorDescription: "cannot proceed your request",
  404. })
  405. }
  406. resp, tokenErr := newAccessTokenResponse(authorizationCode.Grant)
  407. if tokenErr != nil {
  408. handleAccessTokenError(ctx, *tokenErr)
  409. return
  410. }
  411. // send successful response
  412. ctx.JSON(200, resp)
  413. }
  414. func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
  415. ctx.JSON(400, acErr)
  416. }
  417. func handleServerError(ctx *context.Context, state string, redirectURI string) {
  418. handleAuthorizeError(ctx, AuthorizeError{
  419. ErrorCode: ErrorCodeServerError,
  420. ErrorDescription: "A server error occurred",
  421. State: state,
  422. }, redirectURI)
  423. }
  424. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  425. if redirectURI == "" {
  426. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  427. ctx.Data["Error"] = authErr
  428. ctx.HTML(400, tplGrantError)
  429. return
  430. }
  431. redirect, err := url.Parse(redirectURI)
  432. if err != nil {
  433. ctx.ServerError("url.Parse", err)
  434. return
  435. }
  436. q := redirect.Query()
  437. q.Set("error", string(authErr.ErrorCode))
  438. q.Set("error_description", authErr.ErrorDescription)
  439. q.Set("state", authErr.State)
  440. redirect.RawQuery = q.Encode()
  441. ctx.Redirect(redirect.String(), 302)
  442. }