2019-03-08 19:42:50 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package user
import (
2019-03-11 05:54:59 +03:00
"encoding/base64"
2019-03-08 19:42:50 +03:00
"fmt"
2020-08-28 07:37:05 +03:00
"html"
2021-04-05 18:30:52 +03:00
"net/http"
2019-03-08 19:42:50 +03:00
"net/url"
2019-03-11 05:54:59 +03:00
"strings"
2019-03-08 19:42:50 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2019-03-08 19:42:50 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2021-06-09 20:53:16 +03:00
"code.gitea.io/gitea/services/auth"
2021-07-24 13:16:34 +03:00
"code.gitea.io/gitea/services/auth/source/oauth2"
2021-04-06 22:44:05 +03:00
"code.gitea.io/gitea/services/forms"
2019-06-12 22:41:28 +03:00
2021-01-26 18:36:53 +03:00
"gitea.com/go-chi/binding"
2021-07-24 14:00:41 +03:00
"github.com/golang-jwt/jwt"
2019-03-08 19:42:50 +03:00
)
const (
tplGrantAccess base . TplName = "user/auth/grant"
tplGrantError base . TplName = "user/auth/grant_error"
)
// TODO move error and responses to SDK or models
// AuthorizeErrorCode represents an error code specified in RFC 6749
type AuthorizeErrorCode string
const (
// ErrorCodeInvalidRequest represents the according error in RFC 6749
ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request"
// ErrorCodeUnauthorizedClient represents the according error in RFC 6749
ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client"
// ErrorCodeAccessDenied represents the according error in RFC 6749
ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied"
// ErrorCodeUnsupportedResponseType represents the according error in RFC 6749
ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type"
// ErrorCodeInvalidScope represents the according error in RFC 6749
ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope"
// ErrorCodeServerError represents the according error in RFC 6749
ErrorCodeServerError AuthorizeErrorCode = "server_error"
// ErrorCodeTemporaryUnavailable represents the according error in RFC 6749
ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable"
)
// AuthorizeError represents an error type specified in RFC 6749
type AuthorizeError struct {
ErrorCode AuthorizeErrorCode ` json:"error" form:"error" `
ErrorDescription string
State string
}
// Error returns the error message
func ( err AuthorizeError ) Error ( ) string {
return fmt . Sprintf ( "%s: %s" , err . ErrorCode , err . ErrorDescription )
}
// AccessTokenErrorCode represents an error code specified in RFC 6749
type AccessTokenErrorCode string
const (
// AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request"
// AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidClient = "invalid_client"
// AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidGrant = "invalid_grant"
// AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749
AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client"
// AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749
AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type"
// AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidScope = "invalid_scope"
)
// AccessTokenError represents an error response specified in RFC 6749
type AccessTokenError struct {
ErrorCode AccessTokenErrorCode ` json:"error" form:"error" `
ErrorDescription string ` json:"error_description" `
}
// Error returns the error message
func ( err AccessTokenError ) Error ( ) string {
return fmt . Sprintf ( "%s: %s" , err . ErrorCode , err . ErrorDescription )
}
2021-05-06 08:30:15 +03:00
// BearerTokenErrorCode represents an error code specified in RFC 6750
type BearerTokenErrorCode string
const (
// BearerTokenErrorCodeInvalidRequest represents an error code specified in RFC 6750
BearerTokenErrorCodeInvalidRequest BearerTokenErrorCode = "invalid_request"
// BearerTokenErrorCodeInvalidToken represents an error code specified in RFC 6750
BearerTokenErrorCodeInvalidToken BearerTokenErrorCode = "invalid_token"
// BearerTokenErrorCodeInsufficientScope represents an error code specified in RFC 6750
BearerTokenErrorCodeInsufficientScope BearerTokenErrorCode = "insufficient_scope"
)
// BearerTokenError represents an error response specified in RFC 6750
type BearerTokenError struct {
ErrorCode BearerTokenErrorCode ` json:"error" form:"error" `
ErrorDescription string ` json:"error_description" `
}
2019-03-08 19:42:50 +03:00
// TokenType specifies the kind of token
type TokenType string
const (
// TokenTypeBearer represents a token type specified in RFC 6749
TokenTypeBearer TokenType = "bearer"
// TokenTypeMAC represents a token type specified in RFC 6749
TokenTypeMAC = "mac"
)
// AccessTokenResponse represents a successful access token response
type AccessTokenResponse struct {
2019-04-12 10:50:21 +03:00
AccessToken string ` json:"access_token" `
TokenType TokenType ` json:"token_type" `
ExpiresIn int64 ` json:"expires_in" `
RefreshToken string ` json:"refresh_token" `
2021-01-01 19:33:27 +03:00
IDToken string ` json:"id_token,omitempty" `
2019-03-08 19:42:50 +03:00
}
2021-06-18 00:56:46 +03:00
func newAccessTokenResponse ( grant * models . OAuth2Grant , signingKey oauth2 . JWTSigningKey ) ( * AccessTokenResponse , * AccessTokenError ) {
2019-04-12 10:50:21 +03:00
if setting . OAuth2 . InvalidateRefreshTokens {
if err := grant . IncreaseCounter ( ) ; err != nil {
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidGrant ,
ErrorDescription : "cannot increase the grant counter" ,
}
2019-03-08 19:42:50 +03:00
}
}
// generate access token to access the API
2019-08-15 17:46:21 +03:00
expirationDate := timeutil . TimeStampNow ( ) . Add ( setting . OAuth2 . AccessTokenExpirationTime )
2021-07-24 13:16:34 +03:00
accessToken := & oauth2 . Token {
2019-03-08 19:42:50 +03:00
GrantID : grant . ID ,
2021-07-24 13:16:34 +03:00
Type : oauth2 . TypeAccessToken ,
2019-03-08 19:42:50 +03:00
StandardClaims : jwt . StandardClaims {
ExpiresAt : expirationDate . AsTime ( ) . Unix ( ) ,
} ,
}
signedAccessToken , err := accessToken . SignToken ( )
if err != nil {
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot sign token" ,
}
}
// generate refresh token to request an access token after it expired later
2019-08-15 17:46:21 +03:00
refreshExpirationDate := timeutil . TimeStampNow ( ) . Add ( setting . OAuth2 . RefreshTokenExpirationTime * 60 * 60 ) . AsTime ( ) . Unix ( )
2021-07-24 13:16:34 +03:00
refreshToken := & oauth2 . Token {
2019-03-08 19:42:50 +03:00
GrantID : grant . ID ,
Counter : grant . Counter ,
2021-07-24 13:16:34 +03:00
Type : oauth2 . TypeRefreshToken ,
2019-03-08 19:42:50 +03:00
StandardClaims : jwt . StandardClaims {
ExpiresAt : refreshExpirationDate ,
} ,
}
signedRefreshToken , err := refreshToken . SignToken ( )
if err != nil {
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot sign token" ,
}
}
2021-01-01 19:33:27 +03:00
// generate OpenID Connect id_token
signedIDToken := ""
if grant . ScopeContains ( "openid" ) {
app , err := models . GetOAuth2ApplicationByID ( grant . ApplicationID )
if err != nil {
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot find application" ,
}
}
2021-08-19 19:11:30 +03:00
user , err := models . GetUserByID ( grant . UserID )
2021-06-14 13:33:16 +03:00
if err != nil {
if models . IsErrUserNotExist ( err ) {
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot find user" ,
}
}
log . Error ( "Error loading user: %v" , err )
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "server error" ,
}
}
2021-07-24 13:16:34 +03:00
idToken := & oauth2 . OIDCToken {
2021-01-01 19:33:27 +03:00
StandardClaims : jwt . StandardClaims {
ExpiresAt : expirationDate . AsTime ( ) . Unix ( ) ,
Issuer : setting . AppURL ,
Audience : app . ClientID ,
Subject : fmt . Sprint ( grant . UserID ) ,
} ,
Nonce : grant . Nonce ,
}
2021-06-14 13:33:16 +03:00
if grant . ScopeContains ( "profile" ) {
2021-08-19 19:11:30 +03:00
idToken . Name = user . FullName
idToken . PreferredUsername = user . Name
idToken . Profile = user . HTMLURL ( )
idToken . Picture = user . AvatarLink ( )
idToken . Website = user . Website
idToken . Locale = user . Language
idToken . UpdatedAt = user . UpdatedUnix
2021-06-14 13:33:16 +03:00
}
if grant . ScopeContains ( "email" ) {
2021-08-19 19:11:30 +03:00
idToken . Email = user . Email
idToken . EmailVerified = user . IsActive
2021-06-14 13:33:16 +03:00
}
2021-06-18 00:56:46 +03:00
signedIDToken , err = idToken . SignToken ( signingKey )
2021-01-01 19:33:27 +03:00
if err != nil {
return nil , & AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot sign token" ,
}
}
}
2019-03-08 19:42:50 +03:00
return & AccessTokenResponse {
AccessToken : signedAccessToken ,
TokenType : TokenTypeBearer ,
ExpiresIn : setting . OAuth2 . AccessTokenExpirationTime ,
RefreshToken : signedRefreshToken ,
2021-01-01 19:33:27 +03:00
IDToken : signedIDToken ,
2019-03-08 19:42:50 +03:00
} , nil
}
2021-05-06 08:30:15 +03:00
type userInfoResponse struct {
Sub string ` json:"sub" `
Name string ` json:"name" `
Username string ` json:"preferred_username" `
Email string ` json:"email" `
Picture string ` json:"picture" `
}
// InfoOAuth manages request for userinfo endpoint
func InfoOAuth ( ctx * context . Context ) {
header := ctx . Req . Header . Get ( "Authorization" )
auths := strings . Fields ( header )
if len ( auths ) != 2 || auths [ 0 ] != "Bearer" {
ctx . HandleText ( http . StatusUnauthorized , "no valid auth token authorization" )
return
}
2021-06-09 20:53:16 +03:00
uid := auth . CheckOAuthAccessToken ( auths [ 1 ] )
2021-05-06 08:30:15 +03:00
if uid == 0 {
handleBearerTokenError ( ctx , BearerTokenError {
ErrorCode : BearerTokenErrorCodeInvalidToken ,
ErrorDescription : "Access token not assigned to any user" ,
} )
return
}
authUser , err := models . GetUserByID ( uid )
if err != nil {
ctx . ServerError ( "GetUserByID" , err )
return
}
response := & userInfoResponse {
Sub : fmt . Sprint ( authUser . ID ) ,
Name : authUser . FullName ,
Username : authUser . Name ,
Email : authUser . Email ,
Picture : authUser . AvatarLink ( ) ,
}
ctx . JSON ( http . StatusOK , response )
}
2019-03-08 19:42:50 +03:00
// AuthorizeOAuth manages authorize requests
2021-01-26 18:36:53 +03:00
func AuthorizeOAuth ( ctx * context . Context ) {
2021-04-06 22:44:05 +03:00
form := web . GetForm ( ctx ) . ( * forms . AuthorizationForm )
2019-03-08 19:42:50 +03:00
errs := binding . Errors { }
2021-01-26 18:36:53 +03:00
errs = form . Validate ( ctx . Req , errs )
2019-06-12 22:41:28 +03:00
if len ( errs ) > 0 {
errstring := ""
for _ , e := range errs {
errstring += e . Error ( ) + "\n"
}
2019-06-13 07:23:45 +03:00
ctx . ServerError ( "AuthorizeOAuth: Validate: " , fmt . Errorf ( "errors occurred during validation: %s" , errstring ) )
2019-06-12 22:41:28 +03:00
return
}
2019-03-08 19:42:50 +03:00
app , err := models . GetOAuth2ApplicationByClientID ( form . ClientID )
if err != nil {
if models . IsErrOauthClientIDInvalid ( err ) {
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeUnauthorizedClient ,
ErrorDescription : "Client ID not registered" ,
State : form . State ,
} , "" )
return
}
ctx . ServerError ( "GetOAuth2ApplicationByClientID" , err )
return
}
if err := app . LoadUser ( ) ; err != nil {
ctx . ServerError ( "LoadUser" , err )
return
}
if ! app . ContainsRedirectURI ( form . RedirectURI ) {
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeInvalidRequest ,
ErrorDescription : "Unregistered Redirect URI" ,
State : form . State ,
} , "" )
return
}
if form . ResponseType != "code" {
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeUnsupportedResponseType ,
ErrorDescription : "Only code response type is supported." ,
State : form . State ,
} , form . RedirectURI )
return
}
// pkce support
switch form . CodeChallengeMethod {
case "S256" :
case "plain" :
if err := ctx . Session . Set ( "CodeChallengeMethod" , form . CodeChallengeMethod ) ; err != nil {
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeServerError ,
ErrorDescription : "cannot set code challenge method" ,
State : form . State ,
} , form . RedirectURI )
return
}
if err := ctx . Session . Set ( "CodeChallengeMethod" , form . CodeChallenge ) ; err != nil {
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeServerError ,
ErrorDescription : "cannot set code challenge" ,
State : form . State ,
} , form . RedirectURI )
return
}
2020-05-17 15:43:29 +03:00
// Here we're just going to try to release the session early
if err := ctx . Session . Release ( ) ; err != nil {
// we'll tolerate errors here as they *should* get saved elsewhere
log . Error ( "Unable to save changes to the session: %v" , err )
}
2019-03-08 19:42:50 +03:00
case "" :
break
default :
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeInvalidRequest ,
ErrorDescription : "unsupported code challenge method" ,
State : form . State ,
} , form . RedirectURI )
return
}
grant , err := app . GetGrantByUserID ( ctx . User . ID )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
return
}
// Redirect if user already granted access
if grant != nil {
code , err := grant . GenerateNewAuthorizationCode ( form . RedirectURI , form . CodeChallenge , form . CodeChallengeMethod )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
return
}
redirect , err := code . GenerateRedirectURI ( form . State )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
return
}
2021-01-01 19:33:27 +03:00
// Update nonce to reflect the new session
if len ( form . Nonce ) > 0 {
err := grant . SetNonce ( form . Nonce )
if err != nil {
log . Error ( "Unable to update nonce: %v" , err )
}
}
2019-03-08 19:42:50 +03:00
ctx . Redirect ( redirect . String ( ) , 302 )
return
}
// show authorize page to grant access
ctx . Data [ "Application" ] = app
ctx . Data [ "RedirectURI" ] = form . RedirectURI
ctx . Data [ "State" ] = form . State
2021-01-01 19:33:27 +03:00
ctx . Data [ "Scope" ] = form . Scope
ctx . Data [ "Nonce" ] = form . Nonce
2020-08-28 07:37:05 +03:00
ctx . Data [ "ApplicationUserLink" ] = "<a href=\"" + html . EscapeString ( setting . AppURL ) + html . EscapeString ( url . PathEscape ( app . User . LowerName ) ) + "\">@" + html . EscapeString ( app . User . Name ) + "</a>"
ctx . Data [ "ApplicationRedirectDomainHTML" ] = "<strong>" + html . EscapeString ( form . RedirectURI ) + "</strong>"
2019-03-08 19:42:50 +03:00
// TODO document SESSION <=> FORM
2019-06-12 22:41:28 +03:00
err = ctx . Session . Set ( "client_id" , app . ClientID )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
log . Error ( err . Error ( ) )
return
}
err = ctx . Session . Set ( "redirect_uri" , form . RedirectURI )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
log . Error ( err . Error ( ) )
return
}
err = ctx . Session . Set ( "state" , form . State )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
log . Error ( err . Error ( ) )
return
}
2020-05-17 15:43:29 +03:00
// Here we're just going to try to release the session early
if err := ctx . Session . Release ( ) ; err != nil {
// we'll tolerate errors here as they *should* get saved elsewhere
log . Error ( "Unable to save changes to the session: %v" , err )
}
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplGrantAccess )
2019-03-08 19:42:50 +03:00
}
// GrantApplicationOAuth manages the post request submitted when a user grants access to an application
2021-01-26 18:36:53 +03:00
func GrantApplicationOAuth ( ctx * context . Context ) {
2021-04-06 22:44:05 +03:00
form := web . GetForm ( ctx ) . ( * forms . GrantApplicationForm )
2019-03-08 19:42:50 +03:00
if ctx . Session . Get ( "client_id" ) != form . ClientID || ctx . Session . Get ( "state" ) != form . State ||
ctx . Session . Get ( "redirect_uri" ) != form . RedirectURI {
2021-04-05 18:30:52 +03:00
ctx . Error ( http . StatusBadRequest )
2019-03-08 19:42:50 +03:00
return
}
app , err := models . GetOAuth2ApplicationByClientID ( form . ClientID )
if err != nil {
ctx . ServerError ( "GetOAuth2ApplicationByClientID" , err )
return
}
2021-01-01 19:33:27 +03:00
grant , err := app . CreateGrant ( ctx . User . ID , form . Scope )
2019-03-08 19:42:50 +03:00
if err != nil {
handleAuthorizeError ( ctx , AuthorizeError {
State : form . State ,
ErrorDescription : "cannot create grant for user" ,
ErrorCode : ErrorCodeServerError ,
} , form . RedirectURI )
return
}
2021-01-01 19:33:27 +03:00
if len ( form . Nonce ) > 0 {
err := grant . SetNonce ( form . Nonce )
if err != nil {
log . Error ( "Unable to update nonce: %v" , err )
}
}
2019-03-08 19:42:50 +03:00
var codeChallenge , codeChallengeMethod string
codeChallenge , _ = ctx . Session . Get ( "CodeChallenge" ) . ( string )
codeChallengeMethod , _ = ctx . Session . Get ( "CodeChallengeMethod" ) . ( string )
code , err := grant . GenerateNewAuthorizationCode ( form . RedirectURI , codeChallenge , codeChallengeMethod )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
return
}
redirect , err := code . GenerateRedirectURI ( form . State )
if err != nil {
handleServerError ( ctx , form . State , form . RedirectURI )
2019-04-25 14:30:38 +03:00
return
2019-03-08 19:42:50 +03:00
}
ctx . Redirect ( redirect . String ( ) , 302 )
}
2021-04-16 05:32:00 +03:00
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
func OIDCWellKnown ( ctx * context . Context ) {
t := ctx . Render . TemplateLookup ( "user/auth/oidc_wellknown" )
ctx . Resp . Header ( ) . Set ( "Content-Type" , "application/json" )
2021-06-18 00:56:46 +03:00
ctx . Data [ "SigningKey" ] = oauth2 . DefaultSigningKey
2021-04-16 05:32:00 +03:00
if err := t . Execute ( ctx . Resp , ctx . Data ) ; err != nil {
log . Error ( "%v" , err )
ctx . Error ( http . StatusInternalServerError )
}
}
2021-06-18 00:56:46 +03:00
// OIDCKeys generates the JSON Web Key Set
func OIDCKeys ( ctx * context . Context ) {
jwk , err := oauth2 . DefaultSigningKey . ToJWK ( )
if err != nil {
log . Error ( "Error converting signing key to JWK: %v" , err )
ctx . Error ( http . StatusInternalServerError )
return
}
jwk [ "use" ] = "sig"
jwks := map [ string ] [ ] map [ string ] string {
"keys" : {
jwk ,
} ,
}
ctx . Resp . Header ( ) . Set ( "Content-Type" , "application/json" )
2021-07-24 19:03:58 +03:00
enc := json . NewEncoder ( ctx . Resp )
2021-06-18 00:56:46 +03:00
if err := enc . Encode ( jwks ) ; err != nil {
log . Error ( "Failed to encode representation as json. Error: %v" , err )
}
}
2019-03-08 19:42:50 +03:00
// AccessTokenOAuth manages all access token requests by the client
2021-01-26 18:36:53 +03:00
func AccessTokenOAuth ( ctx * context . Context ) {
2021-04-06 22:44:05 +03:00
form := * web . GetForm ( ctx ) . ( * forms . AccessTokenForm )
2019-03-11 05:54:59 +03:00
if form . ClientID == "" {
authHeader := ctx . Req . Header . Get ( "Authorization" )
authContent := strings . SplitN ( authHeader , " " , 2 )
if len ( authContent ) == 2 && authContent [ 0 ] == "Basic" {
payload , err := base64 . StdEncoding . DecodeString ( authContent [ 1 ] )
if err != nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot parse basic auth header" ,
} )
return
}
pair := strings . SplitN ( string ( payload ) , ":" , 2 )
if len ( pair ) != 2 {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot parse basic auth header" ,
} )
return
}
form . ClientID = pair [ 0 ]
form . ClientSecret = pair [ 1 ]
}
}
2021-06-18 00:56:46 +03:00
signingKey := oauth2 . DefaultSigningKey
if signingKey . IsSymmetric ( ) {
clientKey , err := oauth2 . CreateJWTSingingKey ( signingKey . SigningMethod ( ) . Alg ( ) , [ ] byte ( form . ClientSecret ) )
if err != nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "Error creating signing key" ,
} )
return
}
signingKey = clientKey
}
2019-03-08 19:42:50 +03:00
switch form . GrantType {
case "refresh_token" :
2021-06-18 00:56:46 +03:00
handleRefreshToken ( ctx , form , signingKey )
2019-03-08 19:42:50 +03:00
case "authorization_code" :
2021-06-18 00:56:46 +03:00
handleAuthorizationCode ( ctx , form , signingKey )
2019-03-08 19:42:50 +03:00
default :
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnsupportedGrantType ,
ErrorDescription : "Only refresh_token or authorization_code grant type is supported" ,
} )
}
}
2021-06-18 00:56:46 +03:00
func handleRefreshToken ( ctx * context . Context , form forms . AccessTokenForm , signingKey oauth2 . JWTSigningKey ) {
2021-07-24 13:16:34 +03:00
token , err := oauth2 . ParseToken ( form . RefreshToken )
2019-03-08 19:42:50 +03:00
if err != nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnauthorizedClient ,
ErrorDescription : "client is not authorized" ,
} )
return
}
// get grant before increasing counter
grant , err := models . GetOAuth2GrantByID ( token . GrantID )
if err != nil || grant == nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidGrant ,
ErrorDescription : "grant does not exist" ,
} )
return
}
// check if token got already used
2019-04-12 10:50:21 +03:00
if setting . OAuth2 . InvalidateRefreshTokens && ( grant . Counter != token . Counter || token . Counter == 0 ) {
2019-03-08 19:42:50 +03:00
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnauthorizedClient ,
ErrorDescription : "token was already used" ,
} )
log . Warn ( "A client tried to use a refresh token for grant_id = %d was used twice!" , grant . ID )
return
}
2021-06-18 00:56:46 +03:00
accessToken , tokenErr := newAccessTokenResponse ( grant , signingKey )
2019-03-08 19:42:50 +03:00
if tokenErr != nil {
handleAccessTokenError ( ctx , * tokenErr )
return
}
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , accessToken )
2019-03-08 19:42:50 +03:00
}
2021-06-18 00:56:46 +03:00
func handleAuthorizationCode ( ctx * context . Context , form forms . AccessTokenForm , signingKey oauth2 . JWTSigningKey ) {
2019-03-08 19:42:50 +03:00
app , err := models . GetOAuth2ApplicationByClientID ( form . ClientID )
if err != nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidClient ,
2019-03-11 05:54:59 +03:00
ErrorDescription : fmt . Sprintf ( "cannot load client with client id: '%s'" , form . ClientID ) ,
2019-03-08 19:42:50 +03:00
} )
return
}
if ! app . ValidateClientSecret ( [ ] byte ( form . ClientSecret ) ) {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnauthorizedClient ,
ErrorDescription : "client is not authorized" ,
} )
return
}
if form . RedirectURI != "" && ! app . ContainsRedirectURI ( form . RedirectURI ) {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnauthorizedClient ,
ErrorDescription : "client is not authorized" ,
} )
return
}
authorizationCode , err := models . GetOAuth2AuthorizationByCode ( form . Code )
if err != nil || authorizationCode == nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnauthorizedClient ,
ErrorDescription : "client is not authorized" ,
} )
return
}
// check if code verifier authorizes the client, PKCE support
if ! authorizationCode . ValidateCodeChallenge ( form . CodeVerifier ) {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeUnauthorizedClient ,
ErrorDescription : "client is not authorized" ,
} )
return
}
// check if granted for this application
if authorizationCode . Grant . ApplicationID != app . ID {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidGrant ,
ErrorDescription : "invalid grant" ,
} )
return
}
// remove token from database to deny duplicate usage
if err := authorizationCode . Invalidate ( ) ; err != nil {
handleAccessTokenError ( ctx , AccessTokenError {
ErrorCode : AccessTokenErrorCodeInvalidRequest ,
ErrorDescription : "cannot proceed your request" ,
} )
}
2021-06-18 00:56:46 +03:00
resp , tokenErr := newAccessTokenResponse ( authorizationCode . Grant , signingKey )
2019-03-08 19:42:50 +03:00
if tokenErr != nil {
handleAccessTokenError ( ctx , * tokenErr )
return
}
// send successful response
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusOK , resp )
2019-03-08 19:42:50 +03:00
}
func handleAccessTokenError ( ctx * context . Context , acErr AccessTokenError ) {
2021-04-05 18:30:52 +03:00
ctx . JSON ( http . StatusBadRequest , acErr )
2019-03-08 19:42:50 +03:00
}
func handleServerError ( ctx * context . Context , state string , redirectURI string ) {
handleAuthorizeError ( ctx , AuthorizeError {
ErrorCode : ErrorCodeServerError ,
ErrorDescription : "A server error occurred" ,
State : state ,
} , redirectURI )
}
func handleAuthorizeError ( ctx * context . Context , authErr AuthorizeError , redirectURI string ) {
if redirectURI == "" {
log . Warn ( "Authorization failed: %v" , authErr . ErrorDescription )
ctx . Data [ "Error" ] = authErr
ctx . HTML ( 400 , tplGrantError )
return
}
redirect , err := url . Parse ( redirectURI )
if err != nil {
ctx . ServerError ( "url.Parse" , err )
return
}
q := redirect . Query ( )
q . Set ( "error" , string ( authErr . ErrorCode ) )
q . Set ( "error_description" , authErr . ErrorDescription )
q . Set ( "state" , authErr . State )
redirect . RawQuery = q . Encode ( )
ctx . Redirect ( redirect . String ( ) , 302 )
}
2021-05-06 08:30:15 +03:00
func handleBearerTokenError ( ctx * context . Context , beErr BearerTokenError ) {
ctx . Resp . Header ( ) . Set ( "WWW-Authenticate" , fmt . Sprintf ( "Bearer realm=\"\", error=\"%s\", error_description=\"%s\"" , beErr . ErrorCode , beErr . ErrorDescription ) )
switch beErr . ErrorCode {
case BearerTokenErrorCodeInvalidRequest :
ctx . JSON ( http . StatusBadRequest , beErr )
case BearerTokenErrorCodeInvalidToken :
ctx . JSON ( http . StatusUnauthorized , beErr )
case BearerTokenErrorCodeInsufficientScope :
ctx . JSON ( http . StatusForbidden , beErr )
default :
log . Error ( "Invalid BearerTokenErrorCode: %v" , beErr . ErrorCode )
ctx . ServerError ( "Unhandled BearerTokenError" , fmt . Errorf ( "BearerTokenError: error=\"%v\", error_description=\"%v\"" , beErr . ErrorCode , beErr . ErrorDescription ) )
}
}