*: add gRPC server for interacting with storages
This commit is contained in:
		
							
								
								
									
										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 | // Client is a mirrored struct from storage with JSON struct tags and | ||||||
| // Kubernetes type metadata. | // 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 { | type Client struct { | ||||||
| 	k8sapi.TypeMeta   `json:",inline"` | 	k8sapi.TypeMeta   `json:",inline"` | ||||||
| 	k8sapi.ObjectMeta `json:"metadata,omitempty"` | 	k8sapi.ObjectMeta `json:"metadata,omitempty"` | ||||||
|   | |||||||
| @@ -77,13 +77,14 @@ func Open(driverName string, config map[string]string) (Storage, error) { | |||||||
| type Storage interface { | type Storage interface { | ||||||
| 	Close() error | 	Close() error | ||||||
|  |  | ||||||
|  | 	// TODO(ericchiang): Let the storages set the IDs of these objects. | ||||||
| 	CreateAuthRequest(a AuthRequest) error | 	CreateAuthRequest(a AuthRequest) error | ||||||
| 	CreateClient(c Client) error | 	CreateClient(c Client) error | ||||||
| 	CreateAuthCode(c AuthCode) error | 	CreateAuthCode(c AuthCode) error | ||||||
| 	CreateRefresh(r Refresh) error | 	CreateRefresh(r Refresh) error | ||||||
|  |  | ||||||
| 	// TODO(ericchiang): return (T, bool, error) so we can indicate not found | 	// 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) | 	GetAuthRequest(id string) (AuthRequest, error) | ||||||
| 	GetAuthCode(id string) (AuthCode, error) | 	GetAuthCode(id string) (AuthCode, error) | ||||||
| 	GetClient(id string) (Client, error) | 	GetClient(id string) (Client, error) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user