Skip to content

Commit

Permalink
Merge f3222b8 into 1fe9d72
Browse files Browse the repository at this point in the history
  • Loading branch information
carrala committed May 24, 2021
2 parents 1fe9d72 + f3222b8 commit eb31998
Show file tree
Hide file tree
Showing 2 changed files with 329 additions and 0 deletions.
100 changes: 100 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,106 @@ func (c *ToznySDKV3) UnshareSecretFromUsername(ctx context.Context, options Unsh
return nil
}

type UnshareBeforeDeleteOptions struct {
SecretID uuid.UUID
CallerClientID uuid.UUID
Type string
}

// UnshareSecretBeforeDelete unshares the secret with SecretID from every group it's shared with
// and deletes the group if it contains no other secrets.
func (c *ToznySDKV3) UnshareSecretBeforeDelete(ctx context.Context, options UnshareBeforeDeleteOptions) ([]error, error) {
listRequest := storageClient.ListGroupsRequest{
ClientID: options.CallerClientID,
}
listGroupResponse, err := c.StorageClient.ListGroups(ctx, listRequest)
if err != nil {
return nil, err
}
secretID := options.SecretID
var processingErrors []error
// Check each group the calling client belongs to for the secret
for _, group := range listGroupResponse.Groups {
if !ValidToznySecretNamespace(group.Name) {
continue
}
listRequest := storageClient.ListGroupRecordsRequest{
GroupID: group.GroupID,
WriterIDs: []string{options.CallerClientID.String()},
}
// Get all the records shared with the group
listGroupRecords, err := c.StorageClient.GetSharedWithGroup(ctx, listRequest)
if err != nil {
msg := fmt.Errorf("UnshareSecretBeforeDelete: could not access group %s. Err: %+v", group.GroupID, err)
processingErrors = append(processingErrors, msg)
continue
}
numberRecordsInGroup := len(listGroupRecords.ResultList)
// unshare the record from the group
recordRemoveShareRequest := storageClient.RemoveRecordSharedWithGroupRequest{
GroupID: group.GroupID,
RecordType: options.Type,
}
err = c.StorageClient.RemoveSharedRecordWithGroup(ctx, recordRemoveShareRequest)
if err != nil {
msg := fmt.Errorf("UnshareSecretBeforeDelete: failed to remove secret %s from group %s. Err: %+v", secretID, group.GroupID, err)
processingErrors = append(processingErrors, msg)
continue
}
// If the group only contains the secret, delete the group
if numberRecordsInGroup == 1 && listGroupRecords.ResultList[0].Metadata.RecordID == secretID.String() {
deleteGroupOptions := storageClient.DeleteGroupRequest{
GroupID: group.GroupID,
AccountID: group.AccountID,
ClientID: options.CallerClientID,
}
err = c.StorageClient.DeleteGroup(ctx, deleteGroupOptions)
if err != nil {
msg := fmt.Errorf("UnshareSecretBeforeDelete: failed to delete empty group %s. Err: %+v", group.GroupID, err)
processingErrors = append(processingErrors, msg)
}
}
}
return processingErrors, nil
}

type DeleteSecretOptions struct {
WriterID string
SecretID uuid.UUID
RecordType string
}

// DeleteSecret deletes the secret with SecretID. It requires that the calling client is the secret owner.
func (c *ToznySDKV3) DeleteSecret(ctx context.Context, options DeleteSecretOptions) ([]error, error) {
callerClientID, err := uuid.Parse(c.StorageClient.ClientID)
if err != nil {
return nil, fmt.Errorf("DeleteSecret: Client ID must be a valid UUID, got %s", c.StorageClient.ClientID)
}
// Check that the person trying to delete the secret is the owner. If not, return an error
if callerClientID.String() != options.WriterID {
return nil, fmt.Errorf("DeleteSecret: Calling client %s does not own secret %s", options.WriterID, options.SecretID)
}
sharedListOptions := UnshareBeforeDeleteOptions{
SecretID: options.SecretID,
CallerClientID: callerClientID,
Type: options.RecordType,
}
// Unshare the secret from all groups it's shared with
processingErrors, err := c.UnshareSecretBeforeDelete(ctx, sharedListOptions)
if err != nil {
return processingErrors, err
}
deleteRecordOptions := pdsClient.DeleteRecordRequest{
RecordID: options.SecretID.String(),
}
// Delete the secret
err = c.E3dbPDSClient.DeleteRecord(ctx, deleteRecordOptions)
if err != nil {
return processingErrors, err
}
return processingErrors, nil
}

// ExecuteSearch takes the given request and returns all records that match that request. Record data for non-files is decrypted. Files must be downloaded separately
func (c *ToznySDKV3) ExecuteSearch(executorRequest *searchExecutorClient.ExecutorQueryRequest) (*[]pdsClient.ListedRecord, error) {
client := searchExecutorClient.New(c.config)
Expand Down
229 changes: 229 additions & 0 deletions secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ func TestCreateAndViewSecretSucceeds(t *testing.T) {
if secretReq.SecretValue != secretView.SecretValue {
t.Fatalf("SecretValue doesn't match. Created: %s Viewed: %s", secretCreated.Record.Data["secretValue"], secretView.Record.Data["secretValue"])
}
// clean up secret
deleteOptions := DeleteSecretOptions{
WriterID: secretCreated.Record.Metadata.WriterID,
SecretID: secretCreated.SecretID,
RecordType: secretCreated.Record.Metadata.Type,
}
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Deleting the secret failed: %+v", err)
}
}

func TestCreateAndReadFileSecretSucceeds(t *testing.T) {
Expand Down Expand Up @@ -223,6 +233,21 @@ func TestCreateAndReadFileSecretSucceeds(t *testing.T) {
if !compare {
t.Fatalf("%s and %s files do not match", plaintextFileName, decryptedFileName)
}
// clean up secret
deleteOptions := DeleteSecretOptions{
WriterID: createdSecret.Record.Metadata.WriterID,
SecretID: createdSecret.SecretID,
RecordType: createdSecret.Record.Metadata.Type,
}
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Deleting the secret failed: %+v", err)
}
// try to read the file
err = sdk.ReadFile(testCtx, readFileOptions)
if err == nil {
t.Fatal("Reading file should have failed as secret was deleted")
}
}

func TestShareSecretByUsernameSucceeds(t *testing.T) {
Expand Down Expand Up @@ -306,6 +331,16 @@ func TestShareSecretByUsernameSucceeds(t *testing.T) {
if err == nil {
t.Fatal("Expected an error since secret isn't shared")
}
// clean up secret
deleteOptions := DeleteSecretOptions{
WriterID: secretView.Record.Metadata.WriterID,
SecretID: secretView.SecretID,
RecordType: secretView.Record.Metadata.Type,
}
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Deleting the secret failed: %+v", err)
}
}

func TestShareSecretInvalidOptionsFails(t *testing.T) {
Expand Down Expand Up @@ -459,6 +494,16 @@ func TestUnshareSecretFromOwnerFails(t *testing.T) {
if err == nil {
t.Fatal("Should error since username is the secret owner\n")
}
// clean up secret
deleteOptions := DeleteSecretOptions{
WriterID: secret.Record.Metadata.WriterID,
SecretID: secret.SecretID,
RecordType: secret.Record.Metadata.Type,
}
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Deleting the secret failed: %+v", err)
}
}

func TestUnshareTwiceSucceeds(t *testing.T) {
Expand Down Expand Up @@ -533,6 +578,190 @@ func TestUnshareTwiceSucceeds(t *testing.T) {
if err != nil {
t.Fatalf("Unshare secret again failed %+v", err)
}
// clean up secret
deleteOptions := DeleteSecretOptions{
WriterID: secret.Record.Metadata.WriterID,
SecretID: secret.SecretID,
RecordType: secret.Record.Metadata.Type,
}
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Deleting the secret failed: %+v", err)
}
}

func TestDeleteSecretSucceeds(t *testing.T) {
// login id 1
request := TozIDLoginRequest{
Username: username2,
Password: password,
RealmName: realmName,
APIBaseURL: baseURL,
LoginHandler: mfaHandler,
}
sdk, err := GetSDKV3ForTozIDUser(request)
if err != nil {
t.Fatalf("Could not log in %+v", err)
}
// login id 2
request = TozIDLoginRequest{
Username: username,
Password: password,
RealmName: realmName,
APIBaseURL: baseURL,
LoginHandler: mfaHandler,
}
sdk2, err := GetSDKV3ForTozIDUser(request)
if err != nil {
t.Fatalf("Could not log in %+v", err)
}
// create the secret
secretReq := CreateSecretOptions{
SecretName: fmt.Sprintf("client-%s", uuid.New().String()),
SecretType: CredentialSecretType,
SecretValue: uuid.New().String(),
Description: "a credential test",
RealmName: realmName,
}
secret, err := sdk.CreateSecret(testCtx, secretReq)
if err != nil {
t.Fatalf("Could not create secret: Req: %+v Err: %+v", secretReq, err)
}
// share the secret with another id
shareOptions := ShareSecretOptions{
SecretName: secret.SecretName,
SecretType: secret.SecretType,
UsernameToAddWithPermissions: map[string][]string{
username: {"READ_CONTENT"},
},
}
err = sdk.ShareSecretWithUsername(testCtx, shareOptions)
if err != nil {
t.Fatalf("Error sharing with username: Err: %+v\n", err)
}
viewOptions := ViewSecretOptions{
SecretID: secret.SecretID,
MaxSecrets: 1000,
}
// check that both identities can view the secret
_, err = sdk.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
// call delete secret
deleteOptions := DeleteSecretOptions{
WriterID: secret.Record.Metadata.WriterID,
SecretID: secret.SecretID,
RecordType: secret.Record.Metadata.Type,
}
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Delete failed: %+v", err)
}
// check that neither identity can view the secret
_, err = sdk.ViewSecret(testCtx, viewOptions)
if err == nil {
t.Fatal("Secret is still viewable but should be deleted")
}
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err == nil {
t.Fatal("Secret is still viewable but should be deleted")
}
}

func TestNonOwnerDeleteSecretFails(t *testing.T) {
// login id 1
request := TozIDLoginRequest{
Username: username2,
Password: password,
RealmName: realmName,
APIBaseURL: baseURL,
LoginHandler: mfaHandler,
}
sdk, err := GetSDKV3ForTozIDUser(request)
if err != nil {
t.Fatalf("Could not log in %+v", err)
}
// login id 2
request = TozIDLoginRequest{
Username: username,
Password: password,
RealmName: realmName,
APIBaseURL: baseURL,
LoginHandler: mfaHandler,
}
sdk2, err := GetSDKV3ForTozIDUser(request)
if err != nil {
t.Fatalf("Could not log in %+v", err)
}
secretReq := CreateSecretOptions{
SecretName: fmt.Sprintf("client-%s", uuid.New().String()),
SecretType: CredentialSecretType,
SecretValue: uuid.New().String(),
Description: "a credential test",
RealmName: realmName,
}
secret, err := sdk.CreateSecret(testCtx, secretReq)
if err != nil {
t.Fatalf("Could not create secret: Req: %+v Err: %+v", secretReq, err)
}
// share the secret with another id
shareOptions := ShareSecretOptions{
SecretName: secret.SecretName,
SecretType: secret.SecretType,
UsernameToAddWithPermissions: map[string][]string{
username: {"READ_CONTENT"},
},
}
err = sdk.ShareSecretWithUsername(testCtx, shareOptions)
if err != nil {
t.Fatalf("Error sharing with username: Err: %+v\n", err)
}
viewOptions := ViewSecretOptions{
SecretID: secret.SecretID,
MaxSecrets: 1000,
}
// check that both identities can view the secret
_, err = sdk.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
// call delete secret with non-owner
deleteOptions := DeleteSecretOptions{
WriterID: secret.Record.Metadata.WriterID,
SecretID: secret.SecretID,
RecordType: secret.Record.Metadata.Type,
}
_, err = sdk2.DeleteSecret(testCtx, deleteOptions)
if err == nil {
t.Fatal("DeleteSecret should have failed because not called by secret owner")
}
// check that both identities can still view the secret
_, err = sdk.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
// call delete secret with the owner
_, err = sdk.DeleteSecret(testCtx, deleteOptions)
if err != nil {
t.Fatalf("Could not delete secret: %+v", err)
}
_, err = sdk.ViewSecret(testCtx, viewOptions)
if err == nil {
t.Fatalf("ViewSecret should fail if the secret was deleted properly.")
}
}

func mfaHandler(sessionResponse *IdentitySessionIntermediateResponse) (LoginActionData, error) {
Expand Down

0 comments on commit eb31998

Please sign in to comment.