Commit 7e9dc836 authored by rithu leena john's avatar rithu leena john Committed by GitHub

Merge pull request #802 from rithujohn191/token-revocation

api: adding a gRPC call for revoking refresh tokens.
parents b119ffdd 1ec19d4f
This diff is collapsed.
...@@ -30,7 +30,7 @@ message DeleteClientReq { ...@@ -30,7 +30,7 @@ message DeleteClientReq {
string id = 1; string id = 1;
} }
// DeleteClientResp determines if the. // DeleteClientResp determines if the client is deleted successfully.
message DeleteClientResp { message DeleteClientResp {
bool not_found = 1; bool not_found = 1;
} }
...@@ -120,6 +120,19 @@ message ListRefreshResp { ...@@ -120,6 +120,19 @@ message ListRefreshResp {
repeated RefreshTokenRef refresh_tokens = 1; 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. // Dex represents the dex gRPC service.
service Dex { service Dex {
// CreateClient creates a client. // CreateClient creates a client.
...@@ -138,4 +151,8 @@ service Dex { ...@@ -138,4 +151,8 @@ service Dex {
rpc GetVersion(VersionReq) returns (VersionResp) {}; rpc GetVersion(VersionReq) returns (VersionResp) {};
// ListRefresh lists all the refresh token entries for a particular user. // ListRefresh lists all the refresh token entries for a particular user.
rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {}; 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 ( ...@@ -16,7 +16,7 @@ import (
// apiVersion increases every time a new call is added to the API. Clients should use this info // 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. // to determine if the server supports specific features.
const apiVersion = 0 const apiVersion = 1
// NewAPI returns a server which implements the gRPC API interface. // NewAPI returns a server which implements the gRPC API interface.
func NewAPI(s storage.Storage, logger logrus.FieldLogger) api.DexServer { 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. ...@@ -204,13 +204,13 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.
id := new(internal.IDTokenSubject) id := new(internal.IDTokenSubject)
if err := internal.Unmarshal(req.UserId, id); err != nil { if err := internal.Unmarshal(req.UserId, id); err != nil {
d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) 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) offlineSessions, err := d.s.GetOfflineSessions(id.UserId, id.ConnId)
if err != nil { if err != nil {
d.logger.Errorf("api: failed to list refresh tokens: %v", err) 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 var refreshTokenRefs []*api.RefreshTokenRef
...@@ -228,3 +228,39 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api. ...@@ -228,3 +228,39 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.
RefreshTokens: refreshTokenRefs, RefreshTokens: refreshTokenRefs,
}, nil }, 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 ( ...@@ -4,9 +4,12 @@ import (
"context" "context"
"os" "os"
"testing" "testing"
"time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/coreos/dex/api" "github.com/coreos/dex/api"
"github.com/coreos/dex/server/internal"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage/memory" "github.com/coreos/dex/storage/memory"
) )
...@@ -65,3 +68,91 @@ func TestPassword(t *testing.T) { ...@@ -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