Skip to content

Commit

Permalink
Merge pull request #18 from tozny/feature/e3db-660
Browse files Browse the repository at this point in the history
Update SDK Registration Routine
  • Loading branch information
ericmann committed Aug 25, 2017
2 parents bc13297 + 722cad2 commit 15e72ad
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 23 deletions.
69 changes: 64 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ type ClientOpts struct {
ClientEmail string
APIKeyID string
APISecret string
PublicKey publicKey
PrivateKey privateKey
PublicKey PublicKey
PrivateKey PrivateKey
APIBaseURL string
Logging bool
}
Expand All @@ -55,18 +55,37 @@ type Client struct {
akCache map[akCacheKey]secretKey
}

type clientKey struct {
type ClientKey struct {
Curve25519 string `json:"curve25519"`
}

// ClientInfo contains information sent by the E3DB service
// about a client.
type ClientInfo struct {
ClientID string `json:"client_id"`
PublicKey clientKey `json:"public_key"`
PublicKey ClientKey `json:"public_key"`
Validated bool `json:"validated"`
}

// ClientDetails contains information about a newly-registered E3DB client
type ClientDetails struct {
ClientID string `json:"client_id"`
ApiKeyID string `json:"api_key_id"`
ApiSecret string `json:"api_secret"`
PublicKey ClientKey `json:"public_key"`
Name string `json:"name"`
}

type clientRegistrationInfo struct {
Name string `json:"name"`
PublicKey ClientKey `json:"public_key"`
}

type clientRegistrationRequest struct {
Token string `json:"token"`
Client clientRegistrationInfo `json:"client"`
}

// Meta contains meta-information about an E3DB record, such as
// who wrote it, when it was written, and the type of the data stored.
type Meta struct {
Expand Down Expand Up @@ -132,6 +151,46 @@ func GetClient(opts ClientOpts) (*Client, error) {
}, nil
}

// RegisterClient creates a new client for a given InnoVault account
func RegisterClient(registrationToken string, clientName string, publicKey ClientKey, apiURL string) (*ClientDetails, error) {
if apiURL == "" {
apiURL = defaultStorageURL
}

request := &clientRegistrationRequest{
Token: registrationToken,
Client: clientRegistrationInfo{
Name: clientName,
PublicKey: publicKey,
},
}

buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(request)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/account/e3db/clients/register", apiURL), buf)

if err != nil {
return nil, err
}

client := &http.Client{}

resp, err := client.Do(req)
if err != nil {
return nil, err
}

defer closeResp(resp)

var details *ClientDetails
if err := json.NewDecoder(resp.Body).Decode(&details); err != nil {
closeResp(resp)
return nil, err
}

return details, nil
}

func (c *Client) apiURL() string {
if c.Options.APIBaseURL == "" {
return defaultStorageURL
Expand Down Expand Up @@ -245,7 +304,7 @@ func (c *Client) GetClientInfo(ctx context.Context, clientID string) (*ClientInf
// getClientKey queries the E3DB server for a client's public key
// given its client UUID. (This was exported in the Java SDK but
// I'm not sure why since it's rather low level.)
func (c *Client) getClientKey(ctx context.Context, clientID string) (publicKey, error) {
func (c *Client) getClientKey(ctx context.Context, clientID string) (PublicKey, error) {
info, err := c.GetClientInfo(ctx, clientID)
if err != nil {
return nil, err
Expand Down
146 changes: 140 additions & 6 deletions client_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
)

var client *Client
var clientOpts *ClientOpts
var altClient *Client
var clientSharedWithID string

// TestMain bootstraps the environment and sets up our client instance.
Expand All @@ -37,25 +39,65 @@ func dieErr(err error) {
}

func setup() {
opts, err := GetConfig("integration-test")
apiURL := os.Getenv("API_URL")
token := os.Getenv("REGISTRATION_TOKEN")

clientName := "test-client-" + base64Encode(randomSecretKey()[:8])
shareClientName := "share-client-" + base64Encode(randomSecretKey()[:8])

pub, priv, err := GenerateKeyPair()
if err != nil {
dieErr(err)
}
pubKey := ClientKey{Curve25519: base64Encode(pub[:])}

pub2, priv2, err := GenerateKeyPair()
if err != nil {
dieErr(err)
}
pubKey2 := ClientKey{Curve25519: base64Encode(pub2[:])}

opts.Logging = false
clientDetails, err := RegisterClient(token, clientName, pubKey, apiURL)
if err != nil {
dieErr(err)
}

client, err = GetClient(*opts)
shareClientDetails, err := RegisterClient(token, shareClientName, pubKey2, apiURL)
if err != nil {
dieErr(err)
}

// Load another client for later sharing tests
opts, err = GetConfig("integration-test-shared")
clientOpts = &ClientOpts{
ClientID: clientDetails.ClientID,
ClientEmail: "",
APIKeyID: clientDetails.ApiKeyID,
APISecret: clientDetails.ApiSecret,
PublicKey: pub,
PrivateKey: priv,
APIBaseURL: apiURL,
Logging: false,
}

client, err = GetClient(*clientOpts)
if err != nil {
dieErr(err)
}
altClient, err = GetClient(*clientOpts)
if err != nil {
dieErr(err)
}

opts.Logging = false
// Load another client for later sharing tests
opts := &ClientOpts{
ClientID: shareClientDetails.ClientID,
ClientEmail: "",
APIKeyID: shareClientDetails.ApiKeyID,
APISecret: shareClientDetails.ApiSecret,
PublicKey: pub2,
PrivateKey: priv2,
APIBaseURL: apiURL,
Logging: false,
}

var client2 *Client
client2, err = GetClient(*opts)
Expand All @@ -70,6 +112,67 @@ func shutdown() {

}

func TestRegistration(t *testing.T) {
apiURL := os.Getenv("API_URL")
token := os.Getenv("REGISTRATION_TOKEN")

pub, _, err := GenerateKeyPair()
if err != nil {
t.Fatal(err)
}

pubKey := ClientKey{Curve25519: base64Encode(pub[:])}
clientName := "test-client-" + base64Encode(randomSecretKey()[:8])

client, err := RegisterClient(token, clientName, pubKey, apiURL)

if err != nil {
t.Fatal(err)
}

if clientName != client.Name {
t.Errorf("Client name does not match: %s != %s", clientName, client.Name)
}

if pubKey.Curve25519 != client.PublicKey.Curve25519 {
t.Errorf("Client keys do not match: %s != %s", pubKey.Curve25519, client.PublicKey.Curve25519)
}

if client.ClientID == "" {
t.Error("Client ID is not set")
}

if client.ApiKeyID == "" {
t.Error("API Key ID is not set")
}

if client.ApiSecret == "" {
t.Error("API Secret is not set")
}
}

func TestConfig(t *testing.T) {
profile := "p_" + base64Encode(randomSecretKey()[:8])

if ProfileExists(profile) {
t.Error("Profile already exists")
}

err := SaveConfig(profile, clientOpts)
if err != nil {
t.Error("Unable to save profile")
}

newOpts, err := GetConfig(profile)
if err != nil {
t.Error("Unable to re-read profile")
}

if newOpts.ClientID != clientOpts.ClientID {
t.Error("Invalid profile retrieved")
}
}

func TestGetClientInfo(t *testing.T) {
info, err := client.GetClientInfo(context.Background(), client.Options.ClientID)
if err != nil {
Expand Down Expand Up @@ -121,6 +224,37 @@ func TestWriteRead(t *testing.T) {
}
}

func TestWriteReadNoCache(t *testing.T) {
data := make(map[string]string)
data["message"] = "Hello, world!"
rec1, err := client.Write(context.Background(), "test-data", data, nil)
if err != nil {
t.Fatal(err)
}
recordID := rec1.Meta.RecordID

rec2, err := altClient.Read(context.Background(), recordID)
if err != nil {
t.Fatal(err)
}

if rec1.Meta.WriterID != rec2.Meta.WriterID {
t.Errorf("Writer IDs don't match: %s != %s", rec1.Meta.WriterID, rec2.Meta.WriterID)
}

if rec1.Meta.UserID != rec2.Meta.UserID {
t.Errorf("User IDs don't match: %s != %s", rec1.Meta.UserID, rec2.Meta.UserID)
}

if rec1.Meta.Type != rec2.Meta.Type {
t.Errorf("Record types don't match: %s != %s", rec1.Meta.Type, rec2.Meta.Type)
}

if rec1.Data["message"] != rec2.Data["message"] {
t.Errorf("Record field doesn't match: %s != %s", rec1.Data["message"], rec2.Data["message"])
}
}

// TestWriteThenDelete should delete a record
func TestWriteThenDelete(t *testing.T) {
data := make(map[string]string)
Expand Down
77 changes: 77 additions & 0 deletions cmd/e3db/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"bufio"
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
Expand All @@ -23,6 +24,8 @@ import (
"strconv"
"strings"

"golang.org/x/crypto/nacl/box"

"github.com/jawher/mow.cli"
"github.com/tozny/e3db-go"
)
Expand Down Expand Up @@ -590,6 +593,79 @@ func cmdPolicyIncoming(cmd *cli.Cmd) {
}
}

func cmdRegister(cmd *cli.Cmd) {
apiBaseURL := cmd.String(cli.StringOpt{
Name: "api",
Desc: "e3db api base url",
Value: "",
HideValue: true,
})

email := cmd.String(cli.StringArg{
Name: "EMAIL",
Desc: "client e-mail address",
Value: "",
HideValue: true,
})

token := cmd.String(cli.StringArg{
Name: "TOKEN",
Desc: "registration token from the InnoVault admin console",
Value: "",
HideValue: false,
})

cmd.Action = func() {
// Preflight check for existing configuration file to prevent a later
// failure writing the file (since we use O_EXCL) after registration.
if e3db.ProfileExists(*options.Profile) {
var name string
if *options.Profile != "" {
name = *options.Profile
} else {
name = "(default)"
}

dieFmt("register: profile %s already registered", name)
}

// minimally validate that email looks like an email address
_, err := mail.ParseAddress(*email)
if err != nil {
dieErr(err)
}

pub, priv, err := box.GenerateKey(rand.Reader)
if err != nil {
dieErr(err)
}

publicKey := e3db.ClientKey{Curve25519: base64.RawURLEncoding.EncodeToString(pub[:])}

details, err := e3db.RegisterClient(*token, *email, publicKey, *apiBaseURL)

if err != nil {
dieErr(err)
}

info := &e3db.ClientOpts{
ClientID: details.ClientID,
ClientEmail: details.Name,
APIKeyID: details.ApiKeyID,
APISecret: details.ApiSecret,
PublicKey: pub,
PrivateKey: priv,
APIBaseURL: *apiBaseURL,
Logging: false,
}

err = e3db.SaveConfig(*options.Profile, info)
if err != nil {
dieErr(err)
}
}
}

func main() {
app := cli.App("e3db-cli", "E3DB Command Line Interface")

Expand All @@ -598,6 +674,7 @@ func main() {
options.Logging = app.BoolOpt("d debug", false, "enable debug logging")
options.Profile = app.StringOpt("p profile", "", "e3db configuration profile")

app.Command("register", "register a client", cmdRegister)
app.Command("info", "get client information", cmdInfo)
app.Command("ls", "list records", cmdList)
app.Command("write", "write a record", cmdWrite)
Expand Down
Loading

0 comments on commit 15e72ad

Please sign in to comment.