forked from mystiq/dex
*: add gRPC server for interacting with storages
This commit is contained in:
parent
cab271f304
commit
94e26782b4
5 changed files with 217 additions and 1 deletions
136
api/api.go
Normal file
136
api/api.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/coreos/poke/api/apipb"
|
||||
"github.com/coreos/poke/storage"
|
||||
)
|
||||
|
||||
// NewServer returns a gRPC server for talking to a storage.
|
||||
func NewServer(s storage.Storage) apipb.StorageServer {
|
||||
return &server{s}
|
||||
}
|
||||
|
||||
type server struct {
|
||||
storage storage.Storage
|
||||
}
|
||||
|
||||
func fromPBClient(client *apipb.Client) storage.Client {
|
||||
return storage.Client{
|
||||
ID: client.Id,
|
||||
Secret: client.Secret,
|
||||
RedirectURIs: client.RedirectUris,
|
||||
TrustedPeers: client.TrustedPeers,
|
||||
Public: client.Public,
|
||||
Name: client.Name,
|
||||
LogoURL: client.LogoUrl,
|
||||
}
|
||||
}
|
||||
|
||||
func toPBClient(client storage.Client) *apipb.Client {
|
||||
return &apipb.Client{
|
||||
Id: client.ID,
|
||||
Secret: client.Secret,
|
||||
RedirectUris: client.RedirectURIs,
|
||||
TrustedPeers: client.TrustedPeers,
|
||||
Public: client.Public,
|
||||
Name: client.Name,
|
||||
LogoUrl: client.LogoURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) CreateClient(ctx context.Context, req *apipb.CreateClientReq) (*apipb.CreateClientResp, error) {
|
||||
// TODO(ericchiang): Create a more centralized strategy for creating client IDs
|
||||
// and secrets which are restricted based on the storage.
|
||||
client := fromPBClient(req.Client)
|
||||
if client.ID == "" {
|
||||
client.ID = storage.NewNonce()
|
||||
}
|
||||
if client.Secret == "" {
|
||||
client.Secret = storage.NewNonce() + storage.NewNonce()
|
||||
}
|
||||
|
||||
if err := s.storage.CreateClient(client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apipb.CreateClientResp{Client: toPBClient(client)}, nil
|
||||
}
|
||||
|
||||
func (s *server) UpdateClient(ctx context.Context, req *apipb.UpdateClientReq) (*apipb.UpdateClientResp, error) {
|
||||
switch {
|
||||
case req.Id == "":
|
||||
return nil, errors.New("no ID supplied")
|
||||
case req.MakePublic && req.MakePrivate:
|
||||
return nil, errors.New("cannot both make public and private")
|
||||
case req.MakePublic && len(req.RedirectUris) != 0:
|
||||
return nil, errors.New("redirect uris supplied for a public client")
|
||||
}
|
||||
|
||||
var client *storage.Client
|
||||
updater := func(old storage.Client) (storage.Client, error) {
|
||||
if req.MakePublic {
|
||||
old.Public = true
|
||||
}
|
||||
if req.MakePrivate {
|
||||
old.Public = false
|
||||
}
|
||||
if req.Secret != "" {
|
||||
old.Secret = req.Secret
|
||||
}
|
||||
if req.Name != "" {
|
||||
old.Name = req.Name
|
||||
}
|
||||
if req.LogoUrl != "" {
|
||||
old.LogoURL = req.LogoUrl
|
||||
}
|
||||
if len(req.RedirectUris) != 0 {
|
||||
if old.Public {
|
||||
return old, errors.New("public clients cannot have redirect URIs")
|
||||
}
|
||||
old.RedirectURIs = req.RedirectUris
|
||||
}
|
||||
client = &old
|
||||
return old, nil
|
||||
}
|
||||
|
||||
if err := s.storage.UpdateClient(req.Id, updater); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apipb.UpdateClientResp{Client: toPBClient(*client)}, nil
|
||||
}
|
||||
|
||||
func (s *server) DeleteClient(ctx context.Context, req *apipb.DeleteClientReq) (*apipb.DeleteClientReq, error) {
|
||||
if req.Id == "" {
|
||||
return nil, errors.New("no client ID supplied")
|
||||
}
|
||||
if err := s.storage.DeleteClient(req.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apipb.DeleteClientReq{}, nil
|
||||
}
|
||||
|
||||
func (s *server) ListClients(ctx context.Context, req *apipb.ListClientsReq) (*apipb.ListClientsResp, error) {
|
||||
clients, err := s.storage.ListClients()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := make([]*apipb.Client, len(clients))
|
||||
for i, client := range clients {
|
||||
resp[i] = toPBClient(client)
|
||||
}
|
||||
return &apipb.ListClientsResp{Clients: resp}, nil
|
||||
}
|
||||
|
||||
func (s *server) GetClient(ctx context.Context, req *apipb.GetClientReq) (*apipb.GetClientResp, error) {
|
||||
if req.Id == "" {
|
||||
return nil, errors.New("no client ID supplied")
|
||||
}
|
||||
client, err := s.storage.GetClient(req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apipb.GetClientResp{Client: toPBClient(client)}, nil
|
||||
}
|
74
api/apipb/api.proto
Normal file
74
api/apipb/api.proto
Normal file
|
@ -0,0 +1,74 @@
|
|||
syntax = "proto3";
|
||||
|
||||
// Run `make grpc` at the top level directory to regenerate Go source code.
|
||||
|
||||
package apipb;
|
||||
|
||||
message Client {
|
||||
string id = 1;
|
||||
string secret = 2;
|
||||
|
||||
repeated string redirect_uris = 3;
|
||||
repeated string trusted_peers = 4;
|
||||
|
||||
bool public = 5;
|
||||
|
||||
string name = 6;
|
||||
string logo_url = 7;
|
||||
}
|
||||
|
||||
message CreateClientReq {
|
||||
Client client = 1;
|
||||
}
|
||||
|
||||
message CreateClientResp {
|
||||
Client client = 1;
|
||||
}
|
||||
|
||||
message UpdateClientReq {
|
||||
string id = 1;
|
||||
|
||||
// Empty strings indicate that string fields should not be updated.
|
||||
string secret = 2;
|
||||
string name = 3;
|
||||
string logo_url = 4;
|
||||
|
||||
bool make_public = 5;
|
||||
bool make_private = 6;
|
||||
|
||||
// If no redirect URIs are specified, the current redirect URIs are preserved.
|
||||
repeated string redirect_uris = 7;
|
||||
}
|
||||
|
||||
message UpdateClientResp {
|
||||
Client client = 1;
|
||||
}
|
||||
|
||||
message ListClientsReq {
|
||||
}
|
||||
|
||||
message ListClientsResp {
|
||||
repeated Client clients = 1;
|
||||
}
|
||||
|
||||
message DeleteClientReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message DeleteClientResp {}
|
||||
|
||||
message GetClientReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message GetClientResp {
|
||||
Client client = 1;
|
||||
}
|
||||
|
||||
service Storage {
|
||||
rpc CreateClient(CreateClientReq) returns (CreateClientResp) {}
|
||||
rpc DeleteClient(DeleteClientReq) returns (DeleteClientReq) {}
|
||||
rpc GetClient(GetClientReq) returns (GetClientResp) {}
|
||||
rpc ListClients(ListClientsReq) returns (ListClientsResp) {}
|
||||
rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {}
|
||||
}
|
2
api/doc.go
Normal file
2
api/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package api implements a gRPC interface for interacting with a storage.
|
||||
package api
|
|
@ -15,6 +15,9 @@ const keysName = "openid-connect-keys"
|
|||
|
||||
// Client is a mirrored struct from storage with JSON struct tags and
|
||||
// Kubernetes type metadata.
|
||||
//
|
||||
// TODO(ericchiang): Kubernetes has an extremely restricted set of characters it can use for IDs.
|
||||
// Consider base32ing client IDs.
|
||||
type Client struct {
|
||||
k8sapi.TypeMeta `json:",inline"`
|
||||
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
|
|
@ -77,13 +77,14 @@ func Open(driverName string, config map[string]string) (Storage, error) {
|
|||
type Storage interface {
|
||||
Close() error
|
||||
|
||||
// TODO(ericchiang): Let the storages set the IDs of these objects.
|
||||
CreateAuthRequest(a AuthRequest) error
|
||||
CreateClient(c Client) error
|
||||
CreateAuthCode(c AuthCode) error
|
||||
CreateRefresh(r Refresh) error
|
||||
|
||||
// TODO(ericchiang): return (T, bool, error) so we can indicate not found
|
||||
// requests that way.
|
||||
// requests that way instead of using ErrNotFound.
|
||||
GetAuthRequest(id string) (AuthRequest, error)
|
||||
GetAuthCode(id string) (AuthCode, error)
|
||||
GetClient(id string) (Client, error)
|
||||
|
|
Loading…
Reference in a new issue