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 15 kB

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