diff --git a/client.go b/client.go index 807b39a..91777ce 100644 --- a/client.go +++ b/client.go @@ -51,6 +51,7 @@ import ( const ( DefaultStorageURL = "https://api.e3db.com" DefaultEncryptedFileName = "encrypted" + DefaultDownloadedFileName = "downloaded" SecretUUID = "38bb737a-4ce0-5ead-8585-e13ea23b09a6" SecretWriterUsernameMetadataKey = "username" SecretSharedMetadataKey = "shared" @@ -1446,7 +1447,12 @@ func (c *ToznySDKV3) CreateSecret(ctx context.Context, secret CreateSecretOption } var createdRecord *pdsClient.Record if secret.SecretType == "File" { - createdRecord, err = c.WriteFile(ctx, recordType, plain, secret.FileName) + writeFileRequest := WriteFileOptions{ + RecordType: recordType, + Plain: plain, + FileName: secret.FileName, + } + createdRecord, err = c.WriteFile(ctx, writeFileRequest) if err != nil { return nil, err } @@ -1465,43 +1471,49 @@ func (c *ToznySDKV3) CreateSecret(ctx context.Context, secret CreateSecretOption return createdSecret, nil } +type WriteFileOptions struct { + RecordType string + Plain map[string]string + FileName string +} + // WriteFile encrypts the file with specified fileName and uploads it, creating a new record in E3DB -func (c *ToznySDKV3) WriteFile(ctx context.Context, recordType string, plain map[string]string, fileName string) (*pdsClient.Record, error) { +func (c *ToznySDKV3) WriteFile(ctx context.Context, options WriteFileOptions) (*pdsClient.Record, error) { keyRequest := pdsClient.GetOrCreateAccessKeyRequest{ WriterID: c.E3dbPDSClient.ClientID, UserID: c.E3dbPDSClient.ClientID, ReaderID: c.E3dbPDSClient.ClientID, - RecordType: recordType, + RecordType: options.RecordType, } ak, err := c.E3dbPDSClient.GetOrCreateAccessKey(ctx, keyRequest) if err != nil { return nil, err } // Encrypt the file - size, checksum, err := e3dbClients.EncryptFile(fileName, DefaultEncryptedFileName, ak) + size, checksum, err := e3dbClients.EncryptFile(options.FileName, DefaultEncryptedFileName, ak) if err != nil { return nil, err } defer func() { err := os.Remove(DefaultEncryptedFileName) if err != nil { - fmt.Println("CreateSecret: error deleting encrypted file") + fmt.Printf("CreatedSecret: Could not delete %s: %+v", DefaultEncryptedFileName, err) } }() - plain[SecretFilenameMetadataKey] = fileName + options.Plain[SecretFilenameMetadataKey] = options.FileName sizeKB := size / 1024 if sizeKB >= 1 { - plain[SecretFileSizeMetadataKey] = fmt.Sprintf("%d", sizeKB) + options.Plain[SecretFileSizeMetadataKey] = fmt.Sprintf("%d", sizeKB) } else { - plain[SecretFileSizeMetadataKey] = "< 1" + options.Plain[SecretFileSizeMetadataKey] = "< 1" } // Write the whole file recordToWrite := storageClient.Record{ Metadata: storageClient.Meta{ - Type: recordType, + Type: options.RecordType, WriterID: uuid.MustParse(c.StorageClient.ClientID), UserID: uuid.MustParse(c.StorageClient.ClientID), - Plain: plain, + Plain: options.Plain, FileMeta: &storageClient.FileMeta{ Size: int64(size), Checksum: checksum, @@ -1547,6 +1559,53 @@ func (c *ToznySDKV3) WriteFile(ctx context.Context, recordType string, plain map return fileRecord, nil } +type ReadFileOptions struct { + RecordID string + DownloadFileName string +} + +// ReadFile downloads and decrypts the file from the record +func (c *ToznySDKV3) ReadFile(ctx context.Context, options ReadFileOptions) error { + secretUUID, err := uuid.Parse(options.RecordID) + if err != nil { + return err + } + fileResp, err := c.E3dbPDSClient.GetFileRecord(ctx, secretUUID) + if err != nil { + return err + } + fileURL := fileResp.Metadata.FileMeta.FileURL + downloadRequest := file.DownloadRequest{ + URL: fileURL, + EncryptedFileName: DefaultDownloadedFileName, + } + downloadResponse, err := file.DownloadFile(downloadRequest) + if err != nil || downloadResponse != "" { + return fmt.Errorf("ReadFile: Err: %+v Resp: %+v", err, downloadResponse) + } + defer func() { + err := os.Remove(DefaultDownloadedFileName) + if err != nil { + fmt.Printf("ReadFile: Could not delete %s: %+v", DefaultDownloadedFileName, err) + } + }() + keyRequest := pdsClient.GetOrCreateAccessKeyRequest{ + WriterID: c.E3dbPDSClient.ClientID, + UserID: c.E3dbPDSClient.ClientID, + ReaderID: c.E3dbPDSClient.ClientID, + RecordType: fileResp.Metadata.Type, + } + ak, err := c.E3dbPDSClient.GetOrCreateAccessKey(ctx, keyRequest) + if err != nil { + return err + } + err = e3dbClients.DecryptFile(DefaultDownloadedFileName, options.DownloadFileName, ak) + if err != nil { + return err + } + return nil +} + // WriteRecord encrypts the data for the record and creates a new record in E3DB func (c *ToznySDKV3) WriteRecord(ctx context.Context, data map[string]string, recordType string, plain map[string]string) (*pdsClient.Record, error) { recordToWrite := pdsClient.WriteRecordRequest{ diff --git a/secrets_test.go b/secrets_test.go index da2a419..6268798 100644 --- a/secrets_test.go +++ b/secrets_test.go @@ -1,8 +1,10 @@ package e3db import ( + "bytes" "context" "fmt" + "io/ioutil" "os" "testing" @@ -17,6 +19,7 @@ var ( baseURL = os.Getenv("TEST_IDENTITY_API_URL") testCtx = context.Background() plaintextFileName = "plainfile" + decryptedFileName = "decrypted" ) func TestCreateAndListSecrets(t *testing.T) { @@ -172,18 +175,18 @@ func TestCreateAndViewSecretSucceeds(t *testing.T) { t.Fatalf("Could not create secret: Req: %+v Err: %+v", secretReq, err) } viewOptions := ViewSecretOptions{ - SecretID: secretCreated.Record.Metadata.RecordID, + SecretID: secretCreated.SecretID, } secretView, err := sdk.ViewSecret(testCtx, viewOptions) if err != nil { t.Fatalf("Could not view secret: Err: %+v", err) } - if secretReq.SecretValue != secretView.Record.Data["secretValue"] { + if secretReq.SecretValue != secretView.SecretValue { t.Fatalf("SecretValue doesn't match. Created: %s Viewed: %s", secretCreated.Record.Data["secretValue"], secretView.Record.Data["secretValue"]) } } -func TestCreateAndViewFileSecretSucceeds(t *testing.T) { +func TestCreateAndReadFileSecretSucceeds(t *testing.T) { request := TozIDLoginRequest{ Username: username, Password: password, @@ -213,15 +216,41 @@ func TestCreateAndViewFileSecretSucceeds(t *testing.T) { secretReq := CreateSecretOptions{ SecretName: fmt.Sprintf("client-%s", uuid.New().String()), SecretType: "File", - SecretValue: "", Description: "a file test", FileName: plaintextFileName, RealmName: realmName, } - _, err = sdk.CreateSecret(testCtx, secretReq) + createdSecret, err := sdk.CreateSecret(testCtx, secretReq) if err != nil { t.Fatalf("Could not create secret: Req: %+v Err: %+v", secretReq, err) } + readFileOptions := ReadFileOptions{ + RecordID: createdSecret.SecretID, + DownloadFileName: decryptedFileName, + } + err = sdk.ReadFile(testCtx, readFileOptions) + if err != nil { + t.Fatalf("Could not read file: Err: %+v", err) + } + defer func() { + err := os.Remove(decryptedFileName) + if err != nil { + t.Logf("Could not delete %s: %+v", decryptedFileName, err) + } + }() + // Compare plaintext and decrypted file contents + plaintext, err := ioutil.ReadFile(plaintextFileName) + if err != nil { + t.Fatalf("Could not read %s file: %+v", plaintextFileName, err) + } + decrypted, err := ioutil.ReadFile(decryptedFileName) + if err != nil { + t.Fatalf("Could not read %s file: %+v", decryptedFileName, err) + } + compare := bytes.Equal(plaintext, decrypted) + if !compare { + t.Fatalf("%s and %s files do not match", plaintextFileName, decryptedFileName) + } } func mfaHandler(sessionResponse *IdentitySessionIntermediateResponse) (LoginActionData, error) {