api: adding a gRPC call for revoking refresh tokens.
This commit is contained in:
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// apiVersion increases every time a new call is added to the API. Clients should use this info
|
||||
// to determine if the server supports specific features.
|
||||
const apiVersion = 0
|
||||
const apiVersion = 1
|
||||
|
||||
// NewAPI returns a server which implements the gRPC API interface.
|
||||
func NewAPI(s storage.Storage, logger logrus.FieldLogger) api.DexServer {
|
||||
@@ -204,13 +204,13 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.
|
||||
id := new(internal.IDTokenSubject)
|
||||
if err := internal.Unmarshal(req.UserId, id); err != nil {
|
||||
d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err)
|
||||
return nil, fmt.Errorf("unmarshal ID Token subject: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offlineSessions, err := d.s.GetOfflineSessions(id.UserId, id.ConnId)
|
||||
if err != nil {
|
||||
d.logger.Errorf("api: failed to list refresh tokens: %v", err)
|
||||
return nil, fmt.Errorf("list refresh tokens: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var refreshTokenRefs []*api.RefreshTokenRef
|
||||
@@ -228,3 +228,39 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.
|
||||
RefreshTokens: refreshTokenRefs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (*api.RevokeRefreshResp, error) {
|
||||
id := new(internal.IDTokenSubject)
|
||||
if err := internal.Unmarshal(req.UserId, id); err != nil {
|
||||
d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var refreshID string
|
||||
updater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
|
||||
if refreshID = old.Refresh[req.ClientId].ID; refreshID == "" {
|
||||
return old, fmt.Errorf("user does not have a refresh token for the client = %s", req.ClientId)
|
||||
}
|
||||
|
||||
// Remove entry from Refresh list of the OfflineSession object.
|
||||
delete(old.Refresh, req.ClientId)
|
||||
|
||||
return old, nil
|
||||
}
|
||||
|
||||
if err := d.s.UpdateOfflineSessions(id.UserId, id.ConnId, updater); err != nil {
|
||||
if err == storage.ErrNotFound {
|
||||
return &api.RevokeRefreshResp{NotFound: true}, nil
|
||||
}
|
||||
d.logger.Errorf("api: failed to update offline session object: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete the refresh token from the storage
|
||||
if err := d.s.DeleteRefresh(refreshID); err != nil {
|
||||
d.logger.Errorf("failed to delete refresh token: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.RevokeRefreshResp{}, nil
|
||||
}
|
||||
|
@@ -4,9 +4,12 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/dex/api"
|
||||
"github.com/coreos/dex/server/internal"
|
||||
"github.com/coreos/dex/storage"
|
||||
"github.com/coreos/dex/storage/memory"
|
||||
)
|
||||
|
||||
@@ -65,3 +68,91 @@ func TestPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Attempts to list and revoke an exisiting refresh token.
|
||||
func TestRefreshToken(t *testing.T) {
|
||||
logger := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: &logrus.TextFormatter{DisableColors: true},
|
||||
Level: logrus.DebugLevel,
|
||||
}
|
||||
|
||||
s := memory.New(logger)
|
||||
serv := NewAPI(s, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Creating a storage with an existing refresh token and offline session for the user.
|
||||
id := storage.NewID()
|
||||
r := storage.RefreshToken{
|
||||
ID: id,
|
||||
Token: "bar",
|
||||
Nonce: "foo",
|
||||
ClientID: "client_id",
|
||||
ConnectorID: "client_secret",
|
||||
Scopes: []string{"openid", "email", "profile"},
|
||||
CreatedAt: time.Now().UTC().Round(time.Millisecond),
|
||||
LastUsed: time.Now().UTC().Round(time.Millisecond),
|
||||
Claims: storage.Claims{
|
||||
UserID: "1",
|
||||
Username: "jane",
|
||||
Email: "jane.doe@example.com",
|
||||
EmailVerified: true,
|
||||
Groups: []string{"a", "b"},
|
||||
},
|
||||
ConnectorData: []byte(`{"some":"data"}`),
|
||||
}
|
||||
|
||||
if err := s.CreateRefresh(r); err != nil {
|
||||
t.Fatalf("create refresh token: %v", err)
|
||||
}
|
||||
|
||||
tokenRef := storage.RefreshTokenRef{
|
||||
ID: r.ID,
|
||||
ClientID: r.ClientID,
|
||||
CreatedAt: r.CreatedAt,
|
||||
LastUsed: r.LastUsed,
|
||||
}
|
||||
|
||||
session := storage.OfflineSessions{
|
||||
UserID: r.Claims.UserID,
|
||||
ConnID: r.ConnectorID,
|
||||
Refresh: make(map[string]*storage.RefreshTokenRef),
|
||||
}
|
||||
session.Refresh[tokenRef.ClientID] = &tokenRef
|
||||
|
||||
if err := s.CreateOfflineSessions(session); err != nil {
|
||||
t.Fatalf("create offline session: %v", err)
|
||||
}
|
||||
|
||||
subjectString, err := internal.Marshal(&internal.IDTokenSubject{
|
||||
UserId: r.Claims.UserID,
|
||||
ConnId: r.ConnectorID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal offline session ID: %v", err)
|
||||
}
|
||||
|
||||
//Testing the api.
|
||||
listReq := api.ListRefreshReq{
|
||||
UserId: subjectString,
|
||||
}
|
||||
|
||||
if _, err := serv.ListRefresh(ctx, &listReq); err != nil {
|
||||
t.Fatalf("Unable to list refresh tokens for user: %v", err)
|
||||
}
|
||||
|
||||
revokeReq := api.RevokeRefreshReq{
|
||||
UserId: subjectString,
|
||||
ClientId: r.ClientID,
|
||||
}
|
||||
|
||||
resp, err := serv.RevokeRefresh(ctx, &revokeReq)
|
||||
if err != nil || resp.NotFound {
|
||||
t.Fatalf("Unable to revoke refresh tokens for user: %v", err)
|
||||
}
|
||||
|
||||
if resp, _ := serv.ListRefresh(ctx, &listReq); len(resp.RefreshTokens) != 0 {
|
||||
t.Fatalf("Refresh token returned inspite of revoking it.")
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user