From 582efa2fd2d56ee4c70d0d3dbb0c4e3a711365be Mon Sep 17 00:00:00 2001 From: April-May <18632637+AprilMay0@users.noreply.github.com> Date: Thu, 22 May 2025 20:26:38 +0100 Subject: [PATCH] feat(cli): Support creating/updating a Username-Password-Domain static credential (#1525) Co-authored-by: Andrew Gaffney Co-authored-by: Johan Brandhorst-Satzkorn --- api/credentials/custom.go | 44 +++ api/credentials/custom_test.go | 133 +++++++++ internal/cmd/commands.go | 12 + internal/cmd/commands/credentialscmd/funcs.go | 2 + ...sername-password-domain_credentials.gen.go | 278 ++++++++++++++++++ .../username-password-domain_funcs.go | 130 ++++++++ internal/cmd/gencli/input.go | 16 + internal/tests/cli/boundary/_credentials.bash | 33 +++ internal/tests/cli/boundary/credentials.bats | 136 +++++++++ 9 files changed, 784 insertions(+) create mode 100644 api/credentials/custom.go create mode 100644 api/credentials/custom_test.go create mode 100644 internal/cmd/commands/credentialscmd/username-password-domain_credentials.gen.go create mode 100644 internal/cmd/commands/credentialscmd/username-password-domain_funcs.go diff --git a/api/credentials/custom.go b/api/credentials/custom.go new file mode 100644 index 000000000..477744c87 --- /dev/null +++ b/api/credentials/custom.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package credentials + +import ( + "fmt" + "strings" +) + +// ParseUsernameDomain parses the username and domain from the given strings. +// It returns the username, domain, and an error if any. +// The username can be in the format of "username", "username@domain", or "domain\username". +func ParseUsernameDomain(username, domain string) (string, string, error) { + switch { + case username != "" && domain == "": + atCount := strings.Count(username, "@") + if atCount > 1 { + return "", "", fmt.Errorf("invalid format, more than one '@' found") + } + backslashCount := strings.Count(username, "\\") + if backslashCount > 1 { + return "", "", fmt.Errorf("invalid format, more than one '\\' found") + } + if atCount > 0 && backslashCount > 0 { + return "", "", fmt.Errorf("invalid format, both '@' and '\\' found") + } + + // username@domain + if u, d, ok := strings.Cut(username, "@"); ok { + return u, d, nil + } + // domain\username + if d, u, ok := strings.Cut(username, "\\"); ok { + return u, d, nil + } + case username != "" && domain != "": + if strings.Contains(username, "@") || strings.Contains(username, "\\") { + return "", "", fmt.Errorf("username and domain cannot be provided together with a username in the format of username@domain or domain\\username") + } + } + + return username, domain, nil +} diff --git a/api/credentials/custom_test.go b/api/credentials/custom_test.go new file mode 100644 index 000000000..b48a77f01 --- /dev/null +++ b/api/credentials/custom_test.go @@ -0,0 +1,133 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package credentials + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestParseUsernameDomain tests the ParseUsernameDomain function with various cases. +func TestParseUsernameDomain(t *testing.T) { + t.Parallel() + tests := []struct { + name string + username string + domain string + expectedUsername string + expectedDomain string + expectError bool + expectErrorStr string + }{ + { + name: "Empty username and domain", + username: "", + domain: "", + expectedUsername: "", + expectedDomain: "", + expectError: false, + }, + { + name: "Username without domain", + username: "user", + domain: "", + expectedUsername: "user", + expectedDomain: "", + expectError: false, + }, + { + name: "Username and domain", + username: "user", + domain: "domain", + expectedUsername: "user", + expectedDomain: "domain", + expectError: false, + }, + { + name: "Empty username with domain", + username: "", + domain: "domain", + expectedUsername: "", + expectedDomain: "domain", + expectError: false, + }, + { + name: "Username with @domain", + username: "user@domain", + domain: "", + expectedUsername: "user", + expectedDomain: "domain", + expectError: false, + }, + { + name: "Domain\\username format", + username: "domain\\user", + domain: "", + expectedUsername: "user", + expectedDomain: "domain", + expectError: false, + }, + { + name: "Conflicting with username@domain and domain", + username: "user@domain", + domain: "domain2", + expectedUsername: "", + expectedDomain: "", + expectError: true, + expectErrorStr: "username and domain cannot be provided together with a username in the format of username@domain or domain\\username", + }, + { + name: "Conflicting with domain\\username and domain", + username: "domain\\user", + domain: "domain2", + expectedUsername: "", + expectedDomain: "", + expectError: true, + expectErrorStr: "username and domain cannot be provided together with a username in the format of username@domain or domain\\username", + }, + { + name: "Multiple '@'", + username: "user@domain@domain", + domain: "", + expectedUsername: "", + expectedDomain: "", + expectError: true, + expectErrorStr: "invalid format, more than one '@' found", + }, + { + name: "Multiple '\\'", + username: "domain\\domain\\user", + domain: "", + expectedUsername: "", + expectedDomain: "", + expectError: true, + expectErrorStr: "invalid format, more than one '\\' found", + }, + { + name: "Mixing '@' and '\\'", + username: "domain\\user@domain.com", + domain: "", + expectedUsername: "", + expectedDomain: "", + expectError: true, + expectErrorStr: "invalid format, both '@' and '\\' found", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + username, domain, err := ParseUsernameDomain(test.username, test.domain) + if test.expectError { + require.ErrorContains(t, err, test.expectErrorStr) + return + } + + require.NoError(t, err) + require.Equal(t, test.expectedUsername, username) + require.Equal(t, test.expectedDomain, domain) + }) + } +} diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index ffebeab86..97fa0f40a 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -603,6 +603,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "create", } }), + "credentials create username-password-domain": wrapper.Wrap(func() wrapper.WrappableCommand { + return &credentialscmd.UsernamePasswordDomainCommand{ + Command: base.NewCommand(ui, opts...), + Func: "create", + } + }), "credentials create ssh-private-key": wrapper.Wrap(func() wrapper.WrappableCommand { return &credentialscmd.SshPrivateKeyCommand{ Command: base.NewCommand(ui, opts...), @@ -627,6 +633,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "update", } }), + "credentials update username-password-domain": wrapper.Wrap(func() wrapper.WrappableCommand { + return &credentialscmd.UsernamePasswordDomainCommand{ + Command: base.NewCommand(ui, opts...), + Func: "update", + } + }), "credentials update ssh-private-key": wrapper.Wrap(func() wrapper.WrappableCommand { return &credentialscmd.SshPrivateKeyCommand{ Command: base.NewCommand(ui, opts...), diff --git a/internal/cmd/commands/credentialscmd/funcs.go b/internal/cmd/commands/credentialscmd/funcs.go index f6eb80d2f..8a7fd7bb4 100644 --- a/internal/cmd/commands/credentialscmd/funcs.go +++ b/internal/cmd/commands/credentialscmd/funcs.go @@ -15,6 +15,7 @@ import ( const ( usernameFlagName = "username" passwordFlagName = "password" + domainFlagName = "domain" privateKeyFlagName = "private-key" privateKeyPassphraseFlagName = "private-key-passphrase" secretFlagName = "secret" @@ -184,6 +185,7 @@ func printItemTable(item *credentials.Credential, resp *api.Response) string { var keySubstMap = map[string]string{ "username": "Username", "password_hmac": "Password HMAC", + "domain": "Domain", "private_key_hmac": "Private Key HMAC", "private_key_passphrase_hmac": "Private Key Passphrase HMAC", } diff --git a/internal/cmd/commands/credentialscmd/username-password-domain_credentials.gen.go b/internal/cmd/commands/credentialscmd/username-password-domain_credentials.gen.go new file mode 100644 index 000000000..8c4ef08aa --- /dev/null +++ b/internal/cmd/commands/credentialscmd/username-password-domain_credentials.gen.go @@ -0,0 +1,278 @@ +// Code generated by "make cli"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package credentialscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentials" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initUsernamePasswordDomainFlags() { + flagsOnce.Do(func() { + extraFlags := extraUsernamePasswordDomainActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsUsernamePasswordDomainMap[k] = append(flagsUsernamePasswordDomainMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*UsernamePasswordDomainCommand)(nil) + _ cli.CommandAutocomplete = (*UsernamePasswordDomainCommand)(nil) +) + +type UsernamePasswordDomainCommand struct { + *base.Command + + Func string + + plural string + + extraUsernamePasswordDomainCmdVars +} + +func (c *UsernamePasswordDomainCommand) AutocompleteArgs() complete.Predictor { + initUsernamePasswordDomainFlags() + return complete.PredictAnything +} + +func (c *UsernamePasswordDomainCommand) AutocompleteFlags() complete.Flags { + initUsernamePasswordDomainFlags() + return c.Flags().Completions() +} + +func (c *UsernamePasswordDomainCommand) Synopsis() string { + if extra := extraUsernamePasswordDomainSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential" + + synopsisStr = fmt.Sprintf("%s %s", "username-password-domain-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *UsernamePasswordDomainCommand) Help() string { + initUsernamePasswordDomainFlags() + + var helpStr string + helpMap := common.HelpMap("credential") + + switch c.Func { + + default: + + helpStr = c.extraUsernamePasswordDomainHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsUsernamePasswordDomainMap = map[string][]string{ + + "create": {"credential-store-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *UsernamePasswordDomainCommand) Flags() *base.FlagSets { + if len(flagsUsernamePasswordDomainMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "username-password-domain-type credential", flagsUsernamePasswordDomainMap, c.Func) + + extraUsernamePasswordDomainFlagsFunc(c, set, f) + + return set +} + +func (c *UsernamePasswordDomainCommand) Run(args []string) int { + initUsernamePasswordDomainFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + } + + c.plural = "username-password-domain-type credential" + switch c.Func { + case "list": + c.plural = "username-password-domain-type credentials" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsUsernamePasswordDomainMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []credentials.Option + + if strutil.StrListContains(flagsUsernamePasswordDomainMap[c.Func], "credential-store-id") { + switch c.Func { + + case "create": + if c.FlagCredentialStoreId == "" { + c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_ID")) + return base.CommandUserError + } + + } + } + + client, err := c.Client() + if c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + credentialsClient := credentials.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, credentials.DefaultName()) + default: + opts = append(opts, credentials.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, credentials.DefaultDescription()) + default: + opts = append(opts, credentials.WithDescription(c.FlagDescription)) + } + + if c.FlagFilter != "" { + opts = append(opts, credentials.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, credentials.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ok := extraUsernamePasswordDomainFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *credentials.Credential + + var createResult *credentials.CredentialCreateResult + + var updateResult *credentials.CredentialUpdateResult + + switch c.Func { + + case "create": + createResult, err = credentialsClient.Create(c.Context, "username_password_domain", c.FlagCredentialStoreId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = createResult.GetResponse() + item = createResult.GetItem() + + case "update": + updateResult, err = credentialsClient.Update(c.Context, c.FlagId, version, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = updateResult.GetResponse() + item = updateResult.GetItem() + + } + + resp, item, err = executeExtraUsernamePasswordDomainActions(c, resp, item, err, credentialsClient, version, opts) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + + output, err := printCustomUsernamePasswordDomainActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item, resp)) + + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +func (c *UsernamePasswordDomainCommand) checkFuncError(err error) int { + if err == nil { + return 0 + } + if apiErr := api.AsServerError(err); apiErr != nil { + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError +} + +var ( + extraUsernamePasswordDomainActionsFlagsMapFunc = func() map[string][]string { return nil } + extraUsernamePasswordDomainSynopsisFunc = func(*UsernamePasswordDomainCommand) string { return "" } + extraUsernamePasswordDomainFlagsFunc = func(*UsernamePasswordDomainCommand, *base.FlagSets, *base.FlagSet) {} + extraUsernamePasswordDomainFlagsHandlingFunc = func(*UsernamePasswordDomainCommand, *base.FlagSets, *[]credentials.Option) bool { return true } + executeExtraUsernamePasswordDomainActions = func(_ *UsernamePasswordDomainCommand, inResp *api.Response, inItem *credentials.Credential, inErr error, _ *credentials.Client, _ uint32, _ []credentials.Option) (*api.Response, *credentials.Credential, error) { + return inResp, inItem, inErr + } + printCustomUsernamePasswordDomainActionOutput = func(*UsernamePasswordDomainCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentialscmd/username-password-domain_funcs.go b/internal/cmd/commands/credentialscmd/username-password-domain_funcs.go new file mode 100644 index 000000000..c146f9b15 --- /dev/null +++ b/internal/cmd/commands/credentialscmd/username-password-domain_funcs.go @@ -0,0 +1,130 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package credentialscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api/credentials" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/go-secure-stdlib/parseutil" +) + +func init() { + extraUsernamePasswordDomainFlagsFunc = extraUsernamePasswordDomainFlagsFuncImpl + extraUsernamePasswordDomainActionsFlagsMapFunc = extraUsernamePasswordDomainActionsFlagsMapFuncImpl + extraUsernamePasswordDomainFlagsHandlingFunc = extraUsernamePasswordDomainFlagHandlingFuncImpl +} + +type extraUsernamePasswordDomainCmdVars struct { + flagUsername string + flagPassword string + flagDomain string +} + +func extraUsernamePasswordDomainActionsFlagsMapFuncImpl() map[string][]string { + flags := map[string][]string{ + "create": { + usernameFlagName, + passwordFlagName, + domainFlagName, + }, + } + flags["update"] = flags["create"] + return flags +} + +func extraUsernamePasswordDomainFlagsFuncImpl(c *UsernamePasswordDomainCommand, set *base.FlagSets, _ *base.FlagSet) { + f := set.NewFlagSet("Username/Password/Domain Credential Options") + + for _, name := range flagsUsernamePasswordDomainMap[c.Func] { + switch name { + case usernameFlagName: + f.StringVar(&base.StringVar{ + Name: usernameFlagName, + Target: &c.flagUsername, + Usage: "The username associated with the credential. This can be a username, or a username with a domain in the format of username@domain or domain\\username", + }) + case passwordFlagName: + f.StringVar(&base.StringVar{ + Name: passwordFlagName, + Target: &c.flagPassword, + Usage: "The password associated with the credential. This can be a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.", + }) + case domainFlagName: + f.StringVar(&base.StringVar{ + Name: domainFlagName, + Target: &c.flagDomain, + Usage: "The domain associated with the credential. If this is not provided, it will be derived from the username field if the username is in the format of username@domain or domain\\username", + }) + } + } +} + +func extraUsernamePasswordDomainFlagHandlingFuncImpl(c *UsernamePasswordDomainCommand, _ *base.FlagSets, opts *[]credentials.Option) bool { + username, domain, err := credentials.ParseUsernameDomain(c.flagUsername, c.flagDomain) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing username and domain: %v", err)) + return false + } + + switch username { + case "": + default: + *opts = append(*opts, credentials.WithUsernamePasswordDomainCredentialUsername(username)) + } + + switch domain { + case "": + default: + *opts = append(*opts, credentials.WithUsernamePasswordDomainCredentialDomain(domain)) + } + + switch c.flagPassword { + case "": + default: + password, err := parseutil.MustParsePath(c.flagPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Password flag must be used with env:// or file:// syntax") + return false + default: + c.UI.Error(fmt.Sprintf("Error parsing password flag: %v", err)) + return false + } + *opts = append(*opts, credentials.WithUsernamePasswordDomainCredentialPassword(password)) + } + + return true +} + +func (c *UsernamePasswordDomainCommand) extraUsernamePasswordDomainHelpFunc(_ map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credentials create username-password-domain -credential-store-id [options] [args]", + "", + " Create a username password domain credential. Example:", + "", + ` $ boundary credentials create username-password-domain -credential-store-id csvlt_1234567890 -username user -password env://PASSWORD -domain domain`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credentials update username-password-domain [options] [args]", + "", + " Update a username password domain credential given its ID. Example:", + "", + ` $ boundary credentials update username-password-domain -id clvlt_1234567890 -name devops -description "For DevOps usage"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 652146a96..8aa5a610c 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -383,6 +383,22 @@ var inputStructs = map[string][]*cmdInfo{ NeedsSubtypeInCreate: true, PrefixAttributeFieldErrorsWithSubactionPrefix: true, }, + { + ResourceType: resource.Credential.String(), + Pkg: "credentials", + StdActions: []string{"create", "update"}, + SubActionPrefix: "username_password_domain", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "CredentialStore", + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + PrefixAttributeFieldErrorsWithSubactionPrefix: true, + }, { ResourceType: resource.Credential.String(), Pkg: "credentials", diff --git a/internal/tests/cli/boundary/_credentials.bash b/internal/tests/cli/boundary/_credentials.bash index e96d3a997..d29b6910a 100644 --- a/internal/tests/cli/boundary/_credentials.bash +++ b/internal/tests/cli/boundary/_credentials.bash @@ -18,6 +18,39 @@ function create_username_password_credential() { -password env://BP } + +function create_username_password_domain_credential_with_domain() { + local name=$1 + local sid=$2 + local user=$3 + local pass=$4 + local domain=$5 + + export BP="${pass}" + boundary credentials create username-password-domain \ + -name $name \ + -description 'test username password domain credential with domain' \ + -credential-store-id $sid \ + -username $user \ + -password env://BP \ + -domain $domain +} + +function create_username_password_domain_credential() { + local name=$1 + local sid=$2 + local user=$3 + local pass=$4 + + export BP="${pass}" + boundary credentials create username-password-domain \ + -name $name \ + -description 'test username password domain credential with domain parsed from username' \ + -credential-store-id $sid \ + -username $user \ + -password env://BP +} + function create_json_credential() { local name=$1 local sid=$2 diff --git a/internal/tests/cli/boundary/credentials.bats b/internal/tests/cli/boundary/credentials.bats index 7b6b14b65..5d7f3ffbb 100644 --- a/internal/tests/cli/boundary/credentials.bats +++ b/internal/tests/cli/boundary/credentials.bats @@ -7,6 +7,10 @@ load _credentials export NEW_STORE='credentials-test-store' export NEW_CREDENTIAL='test-up' +export NEW_UPD_CREDENTIAL='test-user-pass-domain' +export NEW_UPD_AT_CREDENTIAL='test-at-user-domain-pass' +export NEW_UPD_SLASH_CREDENTIAL='test-slash-domain-user-pass' +export NEW_UPD_CREDENTIAL_DOMAIN='test-domain-user-plus-domain' export NEW_JSON_CREDENTIAL='test-json' @test "boundary/login: can login as default user" { @@ -28,6 +32,42 @@ export NEW_JSON_CREDENTIAL='test-json' [ "$status" -eq 0 ] } +@test "boundary/credentials: can create $NEW_UPD_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential_with_domain $NEW_UPD_CREDENTIAL $csid 'username' 'password' 'domain' + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can create $NEW_UPD_AT_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential $NEW_UPD_AT_CREDENTIAL $csid 'username@domain' 'password' + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can create $NEW_UPD_SLASH_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential $NEW_UPD_SLASH_CREDENTIAL $csid 'domain\username' 'password' + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can not create username@domain with domain $NEW_UPD_CREDENTIAL_DOMAIN credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential_with_domain $NEW_UPD_CREDENTIAL_DOMAIN $csid 'username@domain' 'password' 'domain2' + [ "$status" -ne 0 ] + [[ "$output" == *"Error parsing username and domain"* ]] +} + +@test "boundary/credentials: can not create domain/username with domain $NEW_UPD_CREDENTIAL_DOMAIN credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential_with_domain $NEW_UPD_CREDENTIAL_DOMAIN $csid 'domain\username' 'password' 'domain2' + [ "$status" -ne 0 ] + [[ "$output" == *"Error parsing username and domain"* ]] + +} + @test "boundary/credentials: can create $NEW_JSON_CREDENTIAL credential in $NEW_STORE store" { local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) run create_json_credential $NEW_JSON_CREDENTIAL $csid '-string-kv username=admin -string-kv password=pass' @@ -42,6 +82,27 @@ export NEW_JSON_CREDENTIAL='test-json' [ "$status" -eq 1 ] } +@test "boundary/credentials: can not create already created $NEW_UPD_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential_with_domain $NEW_UPD_CREDENTIAL $csid 'username' 'password' 'domain' + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/credentials: can not create already created $NEW_UPD_AT_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential $NEW_UPD_AT_CREDENTIAL $csid 'username@domain' 'password' + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/credentials: can not create already created $NEW_UPD_SLASH_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_domain_credential $NEW_UPD_SLASH_CREDENTIAL $csid 'domain\username' 'password' + echo "$output" + [ "$status" -eq 1 ] +} + @test "boundary/credentials: can not create already created $NEW_JSON_CREDENTIAL credential" { local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) run create_json_credential $NEW_JSON_CREDENTIAL $csid '-string-kv username=admin -string-kv password=pass' @@ -57,6 +118,30 @@ export NEW_JSON_CREDENTIAL='test-json' [ "$status" -eq 0 ] } +@test "boundary/credentials: can read $NEW_UPD_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can read $NEW_UPD_AT_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_AT_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can read $NEW_UPD_SLASH_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_SLASH_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 0 ] +} + @test "boundary/credentials: can read $NEW_JSON_CREDENTIAL credential" { local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid) @@ -74,6 +159,33 @@ export NEW_JSON_CREDENTIAL='test-json' [ "$status" -eq 0 ] } +@test "boundary/credentials: can delete $NEW_UPD_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_CREDENTIAL $csid) + run delete_credential $cid + echo "$output" + run has_status_code "$output" "204" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can delete $NEW_UPD_AT_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_AT_CREDENTIAL $csid) + run delete_credential $cid + echo "$output" + run has_status_code "$output" "204" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can delete $NEW_UPD_SLASH_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_SLASH_CREDENTIAL $csid) + run delete_credential $cid + echo "$output" + run has_status_code "$output" "204" + [ "$status" -eq 0 ] +} + @test "boundary/credentials: can delete $NEW_JSON_CREDENTIAL credential" { local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) local cid=$(credential_id $NEW_JSON_CREDENTIAL $csid) @@ -119,6 +231,30 @@ export NEW_JSON_CREDENTIAL='test-json' [ "$status" -eq 1 ] } +@test "boundary/credential-stores: can not read deleted $NEW_UPD_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/credential-stores: can not read deleted $NEW_UPD_AT_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_AT_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/credential-stores: can not read deleted $NEW_UPD_SLASH_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_UPD_SLASH_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 1 ] +} + @test "boundary/credential-stores: can delete $NEW_STORE static store" { local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) run delete_credential_store $csid