diff --git a/client.go b/client.go index cae58d6..11e0418 100644 --- a/client.go +++ b/client.go @@ -2122,16 +2122,16 @@ func (c *ToznySDKV3) MakeSecretResponse(secretRecord *pdsClient.Record, groupID return secret } -type ShareSecretInfo struct { +type ShareSecretOptions struct { SecretName string SecretType string UsernameToAddWithPermissions map[string][]string } // ShareSecretWithUsername shares all versions of a specified secret with the user with UsernameToAdd -func (c *ToznySDKV3) ShareSecretWithUsername(ctx context.Context, params ShareSecretInfo) error { - if len(params.UsernameToAddWithPermissions) == 0 { - return fmt.Errorf("ShareSecretWithUsername: Username to add must be provided.") +func (c *ToznySDKV3) ShareSecretWithUsername(ctx context.Context, options ShareSecretOptions) error { + if len(options.UsernameToAddWithPermissions) != 1 { + return fmt.Errorf("ShareSecretWithUsername: One username to add must be provided.") } var clientID uuid.UUID sharingMatrix := make(map[uuid.UUID][]string) @@ -2142,7 +2142,7 @@ func (c *ToznySDKV3) ShareSecretWithUsername(ctx context.Context, params ShareSe // Add default permissions for the calling client to the sharing matrix // If the calling client & permissions were included in UsernameToAddWithPermissions, these will be overwritten sharingMatrix[ownerClientID] = []string{storageClient.ShareContentGroupCapability, storageClient.ReadContentGroupCapability} - for username, permissions := range params.UsernameToAddWithPermissions { + for username, permissions := range options.UsernameToAddWithPermissions { // Find the clientID for the username to add searchParams := identityClient.SearchRealmIdentitiesRequest{ RealmName: c.CurrentIdentity.Realm, @@ -2153,16 +2153,20 @@ func (c *ToznySDKV3) ShareSecretWithUsername(ctx context.Context, params ShareSe return err } if len(identities.SearchedIdentitiesInformation) < 1 { - return fmt.Errorf("ShareSecretWithUser: no identity found with username %s", username) + return fmt.Errorf("ShareSecretWithUser: no identity found within realm %s with username %s", c.CurrentIdentity.Realm, username) } clientID = identities.SearchedIdentitiesInformation[0].ClientID // Add client to the sharing matrix sharingMatrix[clientID] = permissions } + // If user tries to share secret with self, return without failure + if clientID == ownerClientID { + return nil + } // Find or create the group for sharing with UsernameToAdd namespaceOptions := NamespaceOptions{ RealmName: c.CurrentIdentity.Realm, - Namespace: fmt.Sprintf("%s.%s.%s.%s", c.StorageClient.ClientID, clientID, params.SecretName, params.SecretType), + Namespace: fmt.Sprintf("%s.%s.%s.%s", c.StorageClient.ClientID, clientID, options.SecretName, options.SecretType), SharingMatrix: sharingMatrix, } group, err := c.GetOrCreateNamespace(ctx, namespaceOptions) @@ -2170,7 +2174,7 @@ func (c *ToznySDKV3) ShareSecretWithUsername(ctx context.Context, params ShareSe return err } // Share record type with group - recordType := fmt.Sprintf("tozny.secret.%s.%s.%s", SecretUUID, params.SecretType, params.SecretName) + recordType := fmt.Sprintf("tozny.secret.%s.%s.%s", SecretUUID, options.SecretType, options.SecretName) err = c.ShareRecordWithGroup(ctx, recordType, group) if err != nil { return err @@ -2178,7 +2182,7 @@ func (c *ToznySDKV3) ShareSecretWithUsername(ctx context.Context, params ShareSe return nil } -type UnshareSecretInfo struct { +type UnshareSecretOptions struct { SecretName string SecretType string UsernameToRevoke string @@ -2186,14 +2190,14 @@ type UnshareSecretInfo struct { // UnshareSecretFromUsername revokes read access to secrets of provided name & type for this specific user // Calling client must be the owner of the secret for this to succeed. -func (c *ToznySDKV3) UnshareSecretFromUsername(ctx context.Context, params UnshareSecretInfo) error { - if params.UsernameToRevoke == "" { +func (c *ToznySDKV3) UnshareSecretFromUsername(ctx context.Context, options UnshareSecretOptions) error { + if options.UsernameToRevoke == "" { return fmt.Errorf("UnshareSecretFromUsername: Username to revoke must be provided") } // find the clientID for the username searchParams := identityClient.SearchRealmIdentitiesRequest{ RealmName: c.CurrentIdentity.Realm, - IdentityUsernames: []string{params.UsernameToRevoke}, + IdentityUsernames: []string{options.UsernameToRevoke}, } identities, err := c.E3dbIdentityClient.SearchRealmIdentities(ctx, searchParams) if err != nil { @@ -2201,7 +2205,7 @@ func (c *ToznySDKV3) UnshareSecretFromUsername(ctx context.Context, params Unsha } // Username doesn't match an identity, so return an error if len(identities.SearchedIdentitiesInformation) < 1 { - return fmt.Errorf("UnshareSecretFromUsername: no identity found within realm with username %s", params.UsernameToRevoke) + return fmt.Errorf("UnshareSecretFromUsername: no identity found within realm %s with username %s", c.CurrentIdentity.Realm, options.UsernameToRevoke) } // Find the sharing group revokeClientID := identities.SearchedIdentitiesInformation[0].ClientID @@ -2213,7 +2217,7 @@ func (c *ToznySDKV3) UnshareSecretFromUsername(ctx context.Context, params Unsha if revokeClientID == ownerClientID { return fmt.Errorf("UnshareSecretFromUsername: Cannot unshare secret from self") } - groupName := fmt.Sprintf("tozny.secret.%s.%s.%s.%s.%s", c.CurrentIdentity.Realm, ownerClientID, revokeClientID, params.SecretName, params.SecretType) + groupName := fmt.Sprintf("tozny.secret.%s.%s.%s.%s.%s", c.CurrentIdentity.Realm, ownerClientID, revokeClientID, options.SecretName, options.SecretType) listRequest := storageClient.ListGroupsRequest{ GroupNames: []string{groupName}, } @@ -2221,13 +2225,13 @@ func (c *ToznySDKV3) UnshareSecretFromUsername(ctx context.Context, params Unsha if err != nil { return err } - // Group does not exist, so secret isn't shared with this identity and unsharing fails. + // Group does not exist, so secret isn't shared with this identity and unsharing doesn't need to happen. if len(listGroupResponse.Groups) < 1 { - return fmt.Errorf("UnshareSecretWithUsername: Sharing group does not exist, so unsharing failed") + return fmt.Errorf("UnshareSecretFromUsername: sharing group does not exist") } // Unshare secret's record type from the group groupID := listGroupResponse.Groups[0].GroupID - recordType := fmt.Sprintf("tozny.secret.%s.%s.%s", SecretUUID, params.SecretType, params.SecretName) + recordType := fmt.Sprintf("tozny.secret.%s.%s.%s", SecretUUID, options.SecretType, options.SecretName) recordRemoveShareRequest := storageClient.RemoveRecordSharedWithGroupRequest{ GroupID: groupID, RecordType: recordType, diff --git a/secrets_test.go b/secrets_test.go index b0d2e7c..f9fc846 100644 --- a/secrets_test.go +++ b/secrets_test.go @@ -272,7 +272,7 @@ func TestShareSecretByUsernameSucceeds(t *testing.T) { t.Fatal("Expected an error since secret isn't shared") } // id 1 shares the secret with id 2 - shareOptions := ShareSecretInfo{ + shareOptions := ShareSecretOptions{ SecretName: secretCreated.SecretName, SecretType: secretCreated.SecretType, UsernameToAddWithPermissions: map[string][]string{ @@ -292,7 +292,7 @@ func TestShareSecretByUsernameSucceeds(t *testing.T) { t.Fatalf("SecretValue doesn't match. Created: %s Viewed: %s", secretCreated.Record.Data["secretValue"], secretView.Record.Data["secretValue"]) } // id 1 unshares secret from id 2 - unshareOptions := UnshareSecretInfo{ + unshareOptions := UnshareSecretOptions{ SecretName: secretCreated.SecretName, SecretType: secretCreated.SecretType, UsernameToRevoke: username, @@ -329,7 +329,7 @@ func TestShareSecretInvalidOptionsFails(t *testing.T) { t.Fatalf("Error viewing shared secret: %+v", err) } // share secret with a username that doesn't exist - shareOptions := ShareSecretInfo{ + shareOptions := ShareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, UsernameToAddWithPermissions: map[string][]string{ @@ -341,7 +341,7 @@ func TestShareSecretInvalidOptionsFails(t *testing.T) { t.Fatal("Should error since username doesn't exist\n") } // share secret with no one - shareOptions = ShareSecretInfo{ + shareOptions = ShareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, UsernameToAddWithPermissions: map[string][]string{}, @@ -350,6 +350,19 @@ func TestShareSecretInvalidOptionsFails(t *testing.T) { if err == nil { t.Fatal("Should error since no usernames were included to share with\n") } + // share secret with two users + shareOptions = ShareSecretOptions{ + SecretName: secret.SecretName, + SecretType: secret.SecretType, + UsernameToAddWithPermissions: map[string][]string{ + username2: {"SHARE_CONTENT", "READ_CONTENT"}, + "invalid-username": {"SHARE_CONTENT", "READ_CONTENT"}, + }, + } + err = sdk.ShareSecretWithUsername(testCtx, shareOptions) + if err == nil { + t.Fatal("Should error since more than one uername was provided\n") + } } func TestUnshareSecretInvalidOptionsFails(t *testing.T) { @@ -372,7 +385,7 @@ func TestUnshareSecretInvalidOptionsFails(t *testing.T) { if err != nil { t.Fatalf("Error viewing shared secret: %+v", err) } - shareOptions := ShareSecretInfo{ + shareOptions := ShareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, UsernameToAddWithPermissions: map[string][]string{ @@ -384,7 +397,7 @@ func TestUnshareSecretInvalidOptionsFails(t *testing.T) { t.Fatalf("Error sharing with username: Err: %+v\n", err) } // unshare secret with no username provided - unshareOptions := UnshareSecretInfo{ + unshareOptions := UnshareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, } @@ -393,7 +406,7 @@ func TestUnshareSecretInvalidOptionsFails(t *testing.T) { t.Fatal("Should error since username doesn't exist\n") } // unshare secret from secret creator - unshareOptions = UnshareSecretInfo{ + unshareOptions = UnshareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, UsernameToRevoke: username, @@ -405,6 +418,42 @@ func TestUnshareSecretInvalidOptionsFails(t *testing.T) { } func TestUnshareSecretFromOwnerFails(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) + } + 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) + } + // Try to unshare the secret from the calling client + unshareOptions := UnshareSecretOptions{ + SecretName: secret.SecretName, + SecretType: secret.SecretType, + UsernameToRevoke: username2, + } + err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions) + if err == nil { + t.Fatal("Should error since username is the secret owner\n") + } +} + +func TestUnshareTwiceSucceeds(t *testing.T) { // login id 1 request := TozIDLoginRequest{ Username: username2, @@ -440,17 +489,8 @@ func TestUnshareSecretFromOwnerFails(t *testing.T) { if err != nil { t.Fatalf("Could not create secret: Req: %+v Err: %+v", secretReq, err) } - // Try to unshare the secret from the calling client - unshareOptions := UnshareSecretInfo{ - SecretName: secret.SecretName, - SecretType: secret.SecretType, - UsernameToRevoke: username2, - } - err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions) - if err == nil { - t.Fatal("Should error since username is the secret owner\n") - } - shareOptions := ShareSecretInfo{ + // id 1 shares the secret with id 2 + shareOptions := ShareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, UsernameToAddWithPermissions: map[string][]string{ @@ -461,23 +501,29 @@ func TestUnshareSecretFromOwnerFails(t *testing.T) { if err != nil { t.Fatalf("Error sharing with username: Err: %+v\n", err) } + // id 2 tries to view secret -- expect success viewOptions := ViewSecretOptions{ - SecretID: uuid.MustParse(secret.SecretID.String()), + SecretID: uuid.MustParse(secret1ID), MaxSecrets: 1000, } - _, err = sdk.ViewSecret(testCtx, viewOptions) + _, err = sdk2.ViewSecret(testCtx, viewOptions) if err != nil { t.Fatalf("Error viewing shared secret: %+v", err) } - // Viewer of secret tries to unshare the secret from the owner - unshareOptions = UnshareSecretInfo{ + // id 1 unshares secret from id 2 + unshareOptions := UnshareSecretOptions{ SecretName: secret.SecretName, SecretType: secret.SecretType, - UsernameToRevoke: username2, + UsernameToRevoke: username, } - err = sdk2.UnshareSecretFromUsername(testCtx, unshareOptions) - if err == nil { - t.Fatal("Should error since username is the secret owner\n") + err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions) + if err != nil { + t.Fatalf("Unshare secret failed %+v", err) + } + // id 1 unshares secret from id 2 again -- should succeed since the group exists + err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions) + if err != nil { + t.Fatalf("Unshare secret again failed %+v", err) } }