forked from mystiq/dex
fix: return invalid_grant error on claiming token of another client
Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>
This commit is contained in:
parent
c319983ecc
commit
578cb05f7b
2 changed files with 58 additions and 1 deletions
|
@ -76,7 +76,9 @@ func (s *Server) getRefreshTokenFromStorage(clientID string, token *internal.Ref
|
|||
|
||||
if refresh.ClientID != clientID {
|
||||
s.logger.Errorf("client %s trying to claim token for client %s", clientID, refresh.ClientID)
|
||||
return nil, invalidErr
|
||||
// According to https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 Dex should response with the
|
||||
// invalid grant error if token has already been claimed by another client.
|
||||
return nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest}
|
||||
}
|
||||
|
||||
if refresh.Token != token.Token {
|
||||
|
|
|
@ -481,6 +481,47 @@ func makeOAuth2Tests(clientID string, clientSecret string, now func() time.Time)
|
|||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "refresh with different client id",
|
||||
scopes: []string{"openid", "email"},
|
||||
handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {
|
||||
v := url.Values{}
|
||||
v.Add("client_id", clientID)
|
||||
v.Add("client_secret", clientSecret)
|
||||
v.Add("grant_type", "refresh_token")
|
||||
v.Add("refresh_token", "existedrefrestoken")
|
||||
v.Add("scope", "oidc email")
|
||||
resp, err := http.PostForm(p.Endpoint().TokenURL, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
return fmt.Errorf("expected status code %d, got %d", http.StatusBadRequest, resp.StatusCode)
|
||||
}
|
||||
|
||||
var respErr struct {
|
||||
Error string `json:"error"`
|
||||
Description string `json:"error_description"`
|
||||
}
|
||||
|
||||
if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
|
||||
return fmt.Errorf("cannot decode token response: %v", err)
|
||||
}
|
||||
|
||||
if respErr.Error != errInvalidGrant {
|
||||
return fmt.Errorf("expected error %q, got %q", errInvalidGrant, respErr.Error)
|
||||
}
|
||||
|
||||
expectedMsg := "Refresh token is invalid or has already been claimed by another client."
|
||||
if respErr.Description != expectedMsg {
|
||||
return fmt.Errorf("expected error description %q, got %q", expectedMsg, respErr.Description)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// This test ensures that the connector.RefreshConnector interface is being
|
||||
// used when clients request a refresh token.
|
||||
|
@ -792,6 +833,13 @@ func TestOAuth2CodeFlow(t *testing.T) {
|
|||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
if err := s.storage.CreateRefresh(storage.RefreshToken{
|
||||
ID: "existedrefrestoken",
|
||||
ClientID: "unexcistedclientid",
|
||||
}); err != nil {
|
||||
t.Fatalf("failed to create existed refresh token: %v", err)
|
||||
}
|
||||
|
||||
// Create the OAuth2 config.
|
||||
oauth2Config = &oauth2.Config{
|
||||
ClientID: client.ID,
|
||||
|
@ -1570,6 +1618,13 @@ func TestOAuth2DeviceFlow(t *testing.T) {
|
|||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
if err := s.storage.CreateRefresh(storage.RefreshToken{
|
||||
ID: "existedrefrestoken",
|
||||
ClientID: "unexcistedclientid",
|
||||
}); err != nil {
|
||||
t.Fatalf("failed to create existed refresh token: %v", err)
|
||||
}
|
||||
|
||||
// Grab the issuer that we'll reuse for the different endpoints to hit
|
||||
issuer, err := url.Parse(s.issuerURL.String())
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue