Skip to content

Commit

Permalink
Merge 3e0126c into 45bf096
Browse files Browse the repository at this point in the history
  • Loading branch information
carrala committed May 20, 2021
2 parents 45bf096 + 3e0126c commit 172b006
Show file tree
Hide file tree
Showing 2 changed files with 277 additions and 17 deletions.
81 changes: 73 additions & 8 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -2153,31 +2153,96 @@ 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)
if err != nil {
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
}
return nil
}

type UnshareSecretOptions struct {
SecretName string
SecretType string
UsernameToRevoke string
}

// 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, 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{options.UsernameToRevoke},
}
identities, err := c.E3dbIdentityClient.SearchRealmIdentities(ctx, searchParams)
if err != nil {
return err
}
// Username doesn't match an identity, so return an error
if len(identities.SearchedIdentitiesInformation) < 1 {
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
ownerClientID, err := uuid.Parse(c.StorageClient.ClientID)
if err != nil {
return fmt.Errorf("UnshareSecretFromUsername: Client ID must be a valid UUID, got %s", c.StorageClient.ClientID)
}
// return an error if user tries to unshare secret from self
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, options.SecretName, options.SecretType)
listRequest := storageClient.ListGroupsRequest{
GroupNames: []string{groupName},
}
listGroupResponse, err := c.StorageClient.ListGroups(ctx, listRequest)
if err != nil {
return err
}
// 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("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, options.SecretType, options.SecretName)
recordRemoveShareRequest := storageClient.RemoveRecordSharedWithGroupRequest{
GroupID: groupID,
RecordType: recordType,
}
err = c.StorageClient.RemoveSharedRecordWithGroup(ctx, recordRemoveShareRequest)
if err != nil {
return err
}
return 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
213 changes: 204 additions & 9 deletions secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,17 @@ func TestShareSecretByUsernameSucceeds(t *testing.T) {
if err != nil {
t.Fatalf("Could not create secret: Req: %+v Err: %+v", secretReq, err)
}
// id 2 tries to view secret -- expect failure
viewOptions := ViewSecretOptions{
SecretID: secretCreated.SecretID,
MaxSecrets: 1000,
}
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err == nil {
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{
Expand All @@ -274,21 +283,32 @@ func TestShareSecretByUsernameSucceeds(t *testing.T) {
if err != nil {
t.Fatalf("Error sharing with username: Err: %+v\n", err)
}
// id 2 tries to view secret
viewOptions := ViewSecretOptions{
SecretID: secretCreated.SecretID,
MaxSecrets: 1000,
}
// id 2 tries to view secret -- expect success
secretView, err := sdk2.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
if secretReq.SecretValue != secretView.SecretValue {
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 := UnshareSecretOptions{
SecretName: secretCreated.SecretName,
SecretType: secretCreated.SecretType,
UsernameToRevoke: username,
}
err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions)
if err != nil {
t.Fatalf("Unshare secret failed %+v", err)
}
// id 2 tries to view secret -- expect failure
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err == nil {
t.Fatal("Expected an error since secret isn't shared")
}
}

func TestShareSecretInvalidUsernameFails(t *testing.T) {
func TestShareSecretInvalidOptionsFails(t *testing.T) {
request := TozIDLoginRequest{
Username: username,
Password: password,
Expand All @@ -309,7 +329,7 @@ func TestShareSecretInvalidUsernameFails(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{
Expand All @@ -321,7 +341,7 @@ func TestShareSecretInvalidUsernameFails(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{},
Expand All @@ -330,6 +350,181 @@ func TestShareSecretInvalidUsernameFails(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) {
request := TozIDLoginRequest{
Username: username,
Password: password,
RealmName: realmName,
APIBaseURL: baseURL,
LoginHandler: mfaHandler,
}
sdk, err := GetSDKV3ForTozIDUser(request)
if err != nil {
t.Fatalf("Could not log in %+v", err)
}
viewOptions := ViewSecretOptions{
SecretID: uuid.MustParse(secret1ID),
MaxSecrets: 1000,
}
secret, err := sdk.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
shareOptions := ShareSecretOptions{
SecretName: secret.SecretName,
SecretType: secret.SecretType,
UsernameToAddWithPermissions: map[string][]string{
username: {"SHARE_CONTENT", "READ_CONTENT"},
},
}
err = sdk.ShareSecretWithUsername(testCtx, shareOptions)
if err != nil {
t.Fatalf("Error sharing with username: Err: %+v\n", err)
}
// unshare secret with no username provided
unshareOptions := UnshareSecretOptions{
SecretName: secret.SecretName,
SecretType: secret.SecretType,
}
err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions)
if err == nil {
t.Fatal("Should error since username doesn't exist\n")
}
// unshare secret from secret creator
unshareOptions = UnshareSecretOptions{
SecretName: secret.SecretName,
SecretType: secret.SecretType,
UsernameToRevoke: username,
}
err = sdk.UnshareSecretFromUsername(testCtx, unshareOptions)
if err == nil {
t.Fatal("Should error since no usernames were included to share with\n")
}
}

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,
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)
}
// id 1 shares the secret with id 2
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)
}
// id 2 tries to view secret -- expect success
viewOptions := ViewSecretOptions{
SecretID: uuid.MustParse(secret1ID),
MaxSecrets: 1000,
}
_, err = sdk2.ViewSecret(testCtx, viewOptions)
if err != nil {
t.Fatalf("Error viewing shared secret: %+v", err)
}
// id 1 unshares secret from id 2
unshareOptions := UnshareSecretOptions{
SecretName: secret.SecretName,
SecretType: secret.SecretType,
UsernameToRevoke: username,
}
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)
}
}

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

0 comments on commit 172b006

Please sign in to comment.