code.gitea.io/gitea@v1.22.3/tests/integration/oauth_test.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"net/http"
    10  	"testing"
    11  
    12  	"code.gitea.io/gitea/modules/json"
    13  	"code.gitea.io/gitea/modules/setting"
    14  	"code.gitea.io/gitea/routers/web/auth"
    15  	"code.gitea.io/gitea/tests"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestAuthorizeNoClientID(t *testing.T) {
    21  	defer tests.PrepareTestEnv(t)()
    22  	req := NewRequest(t, "GET", "/login/oauth/authorize")
    23  	ctx := loginUser(t, "user2")
    24  	resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
    25  	assert.Contains(t, resp.Body.String(), "Client ID not registered")
    26  }
    27  
    28  func TestAuthorizeUnregisteredRedirect(t *testing.T) {
    29  	defer tests.PrepareTestEnv(t)()
    30  	req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate")
    31  	ctx := loginUser(t, "user1")
    32  	resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
    33  	assert.Contains(t, resp.Body.String(), "Unregistered Redirect URI")
    34  }
    35  
    36  func TestAuthorizeUnsupportedResponseType(t *testing.T) {
    37  	defer tests.PrepareTestEnv(t)()
    38  	req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate")
    39  	ctx := loginUser(t, "user1")
    40  	resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
    41  	u, err := resp.Result().Location()
    42  	assert.NoError(t, err)
    43  	assert.Equal(t, "unsupported_response_type", u.Query().Get("error"))
    44  	assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
    45  }
    46  
    47  func TestAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
    48  	defer tests.PrepareTestEnv(t)()
    49  	req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
    50  	ctx := loginUser(t, "user1")
    51  	resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
    52  	u, err := resp.Result().Location()
    53  	assert.NoError(t, err)
    54  	assert.Equal(t, "invalid_request", u.Query().Get("error"))
    55  	assert.Equal(t, "unsupported code challenge method", u.Query().Get("error_description"))
    56  }
    57  
    58  func TestAuthorizeLoginRedirect(t *testing.T) {
    59  	defer tests.PrepareTestEnv(t)()
    60  	req := NewRequest(t, "GET", "/login/oauth/authorize")
    61  	assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
    62  }
    63  
    64  func TestAuthorizeShow(t *testing.T) {
    65  	defer tests.PrepareTestEnv(t)()
    66  	req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate")
    67  	ctx := loginUser(t, "user4")
    68  	resp := ctx.MakeRequest(t, req, http.StatusOK)
    69  
    70  	htmlDoc := NewHTMLParser(t, resp.Body)
    71  	htmlDoc.AssertElement(t, "#authorize-app", true)
    72  	htmlDoc.GetCSRF()
    73  }
    74  
    75  func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
    76  	defer tests.PrepareTestEnv(t)()
    77  	req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https%3A%2F%2Fexample.com%2Fxyzzy&response_type=code&state=thestate")
    78  	ctx := loginUser(t, "user1")
    79  	resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
    80  	u, err := resp.Result().Location()
    81  	assert.NoError(t, err)
    82  	assert.Equal(t, "thestate", u.Query().Get("state"))
    83  	assert.Truef(t, len(u.Query().Get("code")) > 30, "authorization code '%s' should be longer then 30", u.Query().Get("code"))
    84  	u.RawQuery = ""
    85  	assert.Equal(t, "https://example.com/xyzzy", u.String())
    86  }
    87  
    88  func TestAuthorizePKCERequiredForPublicClient(t *testing.T) {
    89  	defer tests.PrepareTestEnv(t)()
    90  	req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=http%3A%2F%2F127.0.0.1&response_type=code&state=thestate")
    91  	ctx := loginUser(t, "user1")
    92  	resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
    93  	u, err := resp.Result().Location()
    94  	assert.NoError(t, err)
    95  	assert.Equal(t, "invalid_request", u.Query().Get("error"))
    96  	assert.Equal(t, "PKCE is required for public clients", u.Query().Get("error_description"))
    97  }
    98  
    99  func TestAccessTokenExchange(t *testing.T) {
   100  	defer tests.PrepareTestEnv(t)()
   101  	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   102  		"grant_type":    "authorization_code",
   103  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   104  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   105  		"redirect_uri":  "a",
   106  		"code":          "authcode",
   107  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   108  	})
   109  	resp := MakeRequest(t, req, http.StatusOK)
   110  	type response struct {
   111  		AccessToken  string `json:"access_token"`
   112  		TokenType    string `json:"token_type"`
   113  		ExpiresIn    int64  `json:"expires_in"`
   114  		RefreshToken string `json:"refresh_token"`
   115  	}
   116  	parsed := new(response)
   117  
   118  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
   119  	assert.True(t, len(parsed.AccessToken) > 10)
   120  	assert.True(t, len(parsed.RefreshToken) > 10)
   121  }
   122  
   123  func TestAccessTokenExchangeWithPublicClient(t *testing.T) {
   124  	defer tests.PrepareTestEnv(t)()
   125  	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   126  		"grant_type":    "authorization_code",
   127  		"client_id":     "ce5a1322-42a7-11ed-b878-0242ac120002",
   128  		"redirect_uri":  "http://127.0.0.1",
   129  		"code":          "authcodepublic",
   130  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   131  	})
   132  	resp := MakeRequest(t, req, http.StatusOK)
   133  	type response struct {
   134  		AccessToken  string `json:"access_token"`
   135  		TokenType    string `json:"token_type"`
   136  		ExpiresIn    int64  `json:"expires_in"`
   137  		RefreshToken string `json:"refresh_token"`
   138  	}
   139  	parsed := new(response)
   140  
   141  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
   142  	assert.True(t, len(parsed.AccessToken) > 10)
   143  	assert.True(t, len(parsed.RefreshToken) > 10)
   144  }
   145  
   146  func TestAccessTokenExchangeJSON(t *testing.T) {
   147  	defer tests.PrepareTestEnv(t)()
   148  	req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
   149  		"grant_type":    "authorization_code",
   150  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   151  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   152  		"redirect_uri":  "a",
   153  		"code":          "authcode",
   154  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   155  	})
   156  	resp := MakeRequest(t, req, http.StatusOK)
   157  	type response struct {
   158  		AccessToken  string `json:"access_token"`
   159  		TokenType    string `json:"token_type"`
   160  		ExpiresIn    int64  `json:"expires_in"`
   161  		RefreshToken string `json:"refresh_token"`
   162  	}
   163  	parsed := new(response)
   164  
   165  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
   166  	assert.True(t, len(parsed.AccessToken) > 10)
   167  	assert.True(t, len(parsed.RefreshToken) > 10)
   168  }
   169  
   170  func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
   171  	defer tests.PrepareTestEnv(t)()
   172  	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   173  		"grant_type":    "authorization_code",
   174  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   175  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   176  		"redirect_uri":  "a",
   177  		"code":          "authcode",
   178  	})
   179  	resp := MakeRequest(t, req, http.StatusBadRequest)
   180  	parsedError := new(auth.AccessTokenError)
   181  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   182  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   183  	assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
   184  }
   185  
   186  func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
   187  	defer tests.PrepareTestEnv(t)()
   188  	// invalid client id
   189  	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   190  		"grant_type":    "authorization_code",
   191  		"client_id":     "???",
   192  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   193  		"redirect_uri":  "a",
   194  		"code":          "authcode",
   195  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   196  	})
   197  	resp := MakeRequest(t, req, http.StatusBadRequest)
   198  	parsedError := new(auth.AccessTokenError)
   199  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   200  	assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
   201  	assert.Equal(t, "cannot load client with client id: '???'", parsedError.ErrorDescription)
   202  
   203  	// invalid client secret
   204  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   205  		"grant_type":    "authorization_code",
   206  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   207  		"client_secret": "???",
   208  		"redirect_uri":  "a",
   209  		"code":          "authcode",
   210  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   211  	})
   212  	resp = MakeRequest(t, req, http.StatusBadRequest)
   213  	parsedError = new(auth.AccessTokenError)
   214  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   215  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   216  	assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
   217  
   218  	// invalid redirect uri
   219  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   220  		"grant_type":    "authorization_code",
   221  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   222  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   223  		"redirect_uri":  "???",
   224  		"code":          "authcode",
   225  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   226  	})
   227  	resp = MakeRequest(t, req, http.StatusBadRequest)
   228  	parsedError = new(auth.AccessTokenError)
   229  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   230  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   231  	assert.Equal(t, "unexpected redirect URI", parsedError.ErrorDescription)
   232  
   233  	// invalid authorization code
   234  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   235  		"grant_type":    "authorization_code",
   236  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   237  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   238  		"redirect_uri":  "a",
   239  		"code":          "???",
   240  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   241  	})
   242  	resp = MakeRequest(t, req, http.StatusBadRequest)
   243  	parsedError = new(auth.AccessTokenError)
   244  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   245  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   246  	assert.Equal(t, "client is not authorized", parsedError.ErrorDescription)
   247  
   248  	// invalid grant_type
   249  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   250  		"grant_type":    "???",
   251  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   252  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   253  		"redirect_uri":  "a",
   254  		"code":          "authcode",
   255  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   256  	})
   257  	resp = MakeRequest(t, req, http.StatusBadRequest)
   258  	parsedError = new(auth.AccessTokenError)
   259  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   260  	assert.Equal(t, "unsupported_grant_type", string(parsedError.ErrorCode))
   261  	assert.Equal(t, "Only refresh_token or authorization_code grant type is supported", parsedError.ErrorDescription)
   262  }
   263  
   264  func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
   265  	defer tests.PrepareTestEnv(t)()
   266  	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   267  		"grant_type":    "authorization_code",
   268  		"redirect_uri":  "a",
   269  		"code":          "authcode",
   270  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   271  	})
   272  	req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
   273  	resp := MakeRequest(t, req, http.StatusOK)
   274  	type response struct {
   275  		AccessToken  string `json:"access_token"`
   276  		TokenType    string `json:"token_type"`
   277  		ExpiresIn    int64  `json:"expires_in"`
   278  		RefreshToken string `json:"refresh_token"`
   279  	}
   280  	parsed := new(response)
   281  
   282  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
   283  	assert.True(t, len(parsed.AccessToken) > 10)
   284  	assert.True(t, len(parsed.RefreshToken) > 10)
   285  
   286  	// use wrong client_secret
   287  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   288  		"grant_type":    "authorization_code",
   289  		"redirect_uri":  "a",
   290  		"code":          "authcode",
   291  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   292  	})
   293  	req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==")
   294  	resp = MakeRequest(t, req, http.StatusBadRequest)
   295  	parsedError := new(auth.AccessTokenError)
   296  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   297  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   298  	assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
   299  
   300  	// missing header
   301  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   302  		"grant_type":    "authorization_code",
   303  		"redirect_uri":  "a",
   304  		"code":          "authcode",
   305  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   306  	})
   307  	resp = MakeRequest(t, req, http.StatusBadRequest)
   308  	parsedError = new(auth.AccessTokenError)
   309  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   310  	assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
   311  	assert.Equal(t, "cannot load client with client id: ''", parsedError.ErrorDescription)
   312  
   313  	// client_id inconsistent with Authorization header
   314  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   315  		"grant_type":   "authorization_code",
   316  		"redirect_uri": "a",
   317  		"code":         "authcode",
   318  		"client_id":    "inconsistent",
   319  	})
   320  	req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
   321  	resp = MakeRequest(t, req, http.StatusBadRequest)
   322  	parsedError = new(auth.AccessTokenError)
   323  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   324  	assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
   325  	assert.Equal(t, "client_id in request body inconsistent with Authorization header", parsedError.ErrorDescription)
   326  
   327  	// client_secret inconsistent with Authorization header
   328  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   329  		"grant_type":    "authorization_code",
   330  		"redirect_uri":  "a",
   331  		"code":          "authcode",
   332  		"client_secret": "inconsistent",
   333  	})
   334  	req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
   335  	resp = MakeRequest(t, req, http.StatusBadRequest)
   336  	parsedError = new(auth.AccessTokenError)
   337  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   338  	assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
   339  	assert.Equal(t, "client_secret in request body inconsistent with Authorization header", parsedError.ErrorDescription)
   340  }
   341  
   342  func TestRefreshTokenInvalidation(t *testing.T) {
   343  	defer tests.PrepareTestEnv(t)()
   344  	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   345  		"grant_type":    "authorization_code",
   346  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   347  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   348  		"redirect_uri":  "a",
   349  		"code":          "authcode",
   350  		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
   351  	})
   352  	resp := MakeRequest(t, req, http.StatusOK)
   353  	type response struct {
   354  		AccessToken  string `json:"access_token"`
   355  		TokenType    string `json:"token_type"`
   356  		ExpiresIn    int64  `json:"expires_in"`
   357  		RefreshToken string `json:"refresh_token"`
   358  	}
   359  	parsed := new(response)
   360  
   361  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
   362  
   363  	// test without invalidation
   364  	setting.OAuth2.InvalidateRefreshTokens = false
   365  
   366  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   367  		"grant_type": "refresh_token",
   368  		"client_id":  "da7da3ba-9a13-4167-856f-3899de0b0138",
   369  		// omit secret
   370  		"redirect_uri":  "a",
   371  		"refresh_token": parsed.RefreshToken,
   372  	})
   373  	resp = MakeRequest(t, req, http.StatusBadRequest)
   374  	parsedError := new(auth.AccessTokenError)
   375  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   376  	assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
   377  	assert.Equal(t, "invalid empty client secret", parsedError.ErrorDescription)
   378  
   379  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   380  		"grant_type":    "refresh_token",
   381  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   382  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   383  		"redirect_uri":  "a",
   384  		"refresh_token": "UNEXPECTED",
   385  	})
   386  	resp = MakeRequest(t, req, http.StatusBadRequest)
   387  	parsedError = new(auth.AccessTokenError)
   388  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   389  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   390  	assert.Equal(t, "unable to parse refresh token", parsedError.ErrorDescription)
   391  
   392  	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
   393  		"grant_type":    "refresh_token",
   394  		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
   395  		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
   396  		"redirect_uri":  "a",
   397  		"refresh_token": parsed.RefreshToken,
   398  	})
   399  
   400  	bs, err := io.ReadAll(req.Body)
   401  	assert.NoError(t, err)
   402  
   403  	req.Body = io.NopCloser(bytes.NewReader(bs))
   404  	MakeRequest(t, req, http.StatusOK)
   405  
   406  	req.Body = io.NopCloser(bytes.NewReader(bs))
   407  	MakeRequest(t, req, http.StatusOK)
   408  
   409  	// test with invalidation
   410  	setting.OAuth2.InvalidateRefreshTokens = true
   411  	req.Body = io.NopCloser(bytes.NewReader(bs))
   412  	MakeRequest(t, req, http.StatusOK)
   413  
   414  	// repeat request should fail
   415  	req.Body = io.NopCloser(bytes.NewReader(bs))
   416  	resp = MakeRequest(t, req, http.StatusBadRequest)
   417  	parsedError = new(auth.AccessTokenError)
   418  	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
   419  	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
   420  	assert.Equal(t, "token was already used", parsedError.ErrorDescription)
   421  }