Commit 1ec19d4f authored by rithu john's avatar rithu john

api: adding a gRPC call for revoking refresh tokens.

parent b119ffdd
This diff is collapsed.
......@@ -30,7 +30,7 @@ message DeleteClientReq {
string id = 1;
}
// DeleteClientResp determines if the.
// DeleteClientResp determines if the client is deleted successfully.
message DeleteClientResp {
bool not_found = 1;
}
......@@ -120,6 +120,19 @@ message ListRefreshResp {
repeated RefreshTokenRef refresh_tokens = 1;
}
// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.
message RevokeRefreshReq {
// The "sub" claim returned in the ID Token.
string user_id = 1;
string client_id = 2;
}
// RevokeRefreshResp determines if the refresh token is revoked successfully.
message RevokeRefreshResp {
// Set to true is refresh token was not found and token could not be revoked.
bool not_found = 1;
}
// Dex represents the dex gRPC service.
service Dex {
// CreateClient creates a client.
......@@ -138,4 +151,8 @@ service Dex {
rpc GetVersion(VersionReq) returns (VersionResp) {};
// ListRefresh lists all the refresh token entries for a particular user.
rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {};
// RevokeRefresh revokes the refresh token for the provided user-client pair.
//
// Note that each user-client pair can have only one refresh token at a time.
rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {};
}
......@@ -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.")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment