Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete a secret #63

Merged
merged 21 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,105 @@ 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)
carrala marked this conversation as resolved.
Show resolved Hide resolved
}
// 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