diff --git a/src/cli/config/config_test.go b/src/cli/config/config_test.go index 5d9fc42ce..e4f61803e 100644 --- a/src/cli/config/config_test.go +++ b/src/cli/config/config_test.go @@ -307,7 +307,7 @@ func (suite *ConfigSuite) TestReadFromFlags() { flags.AWSSecretAccessKeyFV = "" flags.AWSSessionTokenFV = "" - flags.CorsoPassphraseFV = "" + flags.PassphraseFV = "" }) // Generate test config file @@ -344,7 +344,7 @@ func (suite *ConfigSuite) TestReadFromFlags() { overrides[credentials.AWSSecretAccessKey] = flags.AWSSecretAccessKeyFV overrides[credentials.AWSSessionToken] = flags.AWSSessionTokenFV - flags.CorsoPassphraseFV = "passphrase-flags" + flags.PassphraseFV = "passphrase-flags" repoDetails, err := getStorageAndAccountWithViper( vpr, @@ -378,7 +378,7 @@ func (suite *ConfigSuite) TestReadFromFlags() { assert.Equal(t, flags.AzureClientSecretFV, m365Config.AzureClientSecret) assert.Equal(t, flags.AzureClientTenantFV, m365Config.AzureTenantID) - assert.Equal(t, flags.CorsoPassphraseFV, pass) + assert.Equal(t, flags.PassphraseFV, pass) } // ------------------------------------------------------------ diff --git a/src/cli/config/storage.go b/src/cli/config/storage.go index ed321dd7e..09e4b9c87 100644 --- a/src/cli/config/storage.go +++ b/src/cli/config/storage.go @@ -76,7 +76,7 @@ func configureStorage( func GetAndInsertCorso(passphase string) credentials.Corso { // fetch data from flag, env var or func param giving priority to func param // Func param generally will be value fetched from config file using viper. - corsoPassph := str.First(flags.CorsoPassphraseFV, os.Getenv(credentials.CorsoPassphrase), passphase) + corsoPassph := str.First(flags.PassphraseFV, os.Getenv(credentials.CorsoPassphrase), passphase) return credentials.Corso{ CorsoPassphrase: corsoPassph, diff --git a/src/cli/export/onedrive_test.go b/src/cli/export/onedrive_test.go index 0afe6c437..cde83b81d 100644 --- a/src/cli/export/onedrive_test.go +++ b/src/cli/export/onedrive_test.go @@ -85,7 +85,7 @@ func (suite *OneDriveUnitSuite) TestAddOneDriveCommands() { assert.Equal(t, flagsTD.FileCreatedBeforeInput, opts.FileCreatedBefore) assert.Equal(t, flagsTD.FileModifiedAfterInput, opts.FileModifiedAfter) assert.Equal(t, flagsTD.FileModifiedBeforeInput, opts.FileModifiedBefore) - assert.Equal(t, flagsTD.CorsoPassphrase, flags.CorsoPassphraseFV) + assert.Equal(t, flagsTD.CorsoPassphrase, flags.PassphraseFV) flagsTD.AssertStorageFlags(t, cmd) }) } diff --git a/src/cli/flags/repo.go b/src/cli/flags/repo.go index 77495c08d..62e6e5a81 100644 --- a/src/cli/flags/repo.go +++ b/src/cli/flags/repo.go @@ -12,7 +12,8 @@ const ( AWSSessionTokenFN = "aws-session-token" // Corso Flags - CorsoPassphraseFN = "passphrase" + PassphraseFN = "passphrase" + NewPassphraseFN = "new-passphrase" SucceedIfExistsFN = "succeed-if-exists" ) @@ -22,7 +23,8 @@ var ( AWSAccessKeyFV string AWSSecretAccessKeyFV string AWSSessionTokenFV string - CorsoPassphraseFV string + PassphraseFV string + NewPhasephraseFV string SucceedIfExistsFV bool ) @@ -67,12 +69,21 @@ func AddAWSCredsFlags(cmd *cobra.Command) { // M365 flags func AddCorsoPassphaseFlags(cmd *cobra.Command) { fs := cmd.Flags() - fs.StringVar(&CorsoPassphraseFV, - CorsoPassphraseFN, + fs.StringVar(&PassphraseFV, + PassphraseFN, "", "Passphrase to protect encrypted repository contents") } +// M365 flags +func AddCorsoUpdatePassphraseFlags(cmd *cobra.Command) { + fs := cmd.Flags() + fs.StringVar(&NewPhasephraseFV, + NewPassphraseFN, + "", + "update Corso passphrase for repo") +} + // --------------------------------------------------------------------------- // Provider // --------------------------------------------------------------------------- diff --git a/src/cli/flags/testdata/repo.go b/src/cli/flags/testdata/repo.go index 9ac0481f0..98f99ec20 100644 --- a/src/cli/flags/testdata/repo.go +++ b/src/cli/flags/testdata/repo.go @@ -15,7 +15,7 @@ func PreparedStorageFlags() []string { "--" + flags.AWSSecretAccessKeyFN, AWSSecretAccessKey, "--" + flags.AWSSessionTokenFN, AWSSessionToken, - "--" + flags.CorsoPassphraseFN, CorsoPassphrase, + "--" + flags.PassphraseFN, CorsoPassphrase, } } @@ -24,7 +24,7 @@ func AssertStorageFlags(t *testing.T, cmd *cobra.Command) { assert.Equal(t, AWSSecretAccessKey, flags.AWSSecretAccessKeyFV) assert.Equal(t, AWSSessionToken, flags.AWSSessionTokenFV) - assert.Equal(t, CorsoPassphrase, flags.CorsoPassphraseFV) + assert.Equal(t, CorsoPassphrase, flags.PassphraseFV) } func PreparedProviderFlags() []string { diff --git a/src/cli/repo/repo.go b/src/cli/repo/repo.go index 04b3a8413..1a7338b6e 100644 --- a/src/cli/repo/repo.go +++ b/src/cli/repo/repo.go @@ -8,16 +8,24 @@ import ( "golang.org/x/exp/maps" "github.com/alcionai/corso/src/cli/flags" - "github.com/alcionai/corso/src/cli/print" + . "github.com/alcionai/corso/src/cli/print" "github.com/alcionai/corso/src/cli/utils" + "github.com/alcionai/corso/src/internal/events" "github.com/alcionai/corso/src/pkg/control/repository" "github.com/alcionai/corso/src/pkg/path" + repo "github.com/alcionai/corso/src/pkg/repository" ) const ( - initCommand = "init" - connectCommand = "connect" - maintenanceCommand = "maintenance" + initCommand = "init" + connectCommand = "connect" + updatePassphraseCommand = "update-passphrase" + MaintenanceCommand = "maintenance" +) + +const ( + providerCommandUpdatePhasephraseExamples = `# Update the Corso repository passphrase" +corso repo update-passphrase --new-passphrase 'newpass'` ) var ( @@ -35,16 +43,18 @@ func AddCommands(cmd *cobra.Command) { var ( // Get new instances so that setting the context during tests works // properly. - repoCmd = repoCmd() - initCmd = initCmd() - connectCmd = connectCmd() - maintenanceCmd = maintenanceCmd() + repoCmd = repoCmd() + initCmd = initCmd() + connectCmd = connectCmd() + maintenanceCmd = maintenanceCmd() + updatePassphraseCmd = updatePassphraseCmd() ) cmd.AddCommand(repoCmd) repoCmd.AddCommand(initCmd) repoCmd.AddCommand(connectCmd) repoCmd.AddCommand(maintenanceCmd) + repoCmd.AddCommand(updatePassphraseCmd) flags.AddMaintenanceModeFlag(maintenanceCmd) flags.AddForceMaintenanceFlag(maintenanceCmd) @@ -63,7 +73,7 @@ func repoCmd() *cobra.Command { return &cobra.Command{ Use: "repo", Short: "Manage your repositories", - Long: `Initialize, configure, and connect to your account backup repositories.`, + Long: `Initialize, configure, connect and update to your account backup repositories`, RunE: handleRepoCmd, Args: cobra.NoArgs, } @@ -111,7 +121,7 @@ func handleConnectCmd(cmd *cobra.Command, args []string) error { func maintenanceCmd() *cobra.Command { return &cobra.Command{ - Use: maintenanceCommand, + Use: MaintenanceCommand, Short: "Run maintenance on an existing repository", Long: `Run maintenance on an existing repository to optimize performance and storage use`, RunE: handleMaintenanceCmd, @@ -134,7 +144,7 @@ func handleMaintenanceCmd(cmd *cobra.Command, args []string) error { // we don't need the graph client. path.OneDriveService) if err != nil { - return print.Only(ctx, err) + return Only(ctx, err) } defer utils.CloseRepo(ctx, r) @@ -147,12 +157,12 @@ func handleMaintenanceCmd(cmd *cobra.Command, args []string) error { Force: flags.ForceMaintenanceFV, }) if err != nil { - return print.Only(ctx, err) + return Only(ctx, err) } err = m.Run(ctx) if err != nil { - return print.Only(ctx, err) + return Only(ctx, err) } return nil @@ -175,3 +185,55 @@ func getMaintenanceType(t string) (repository.MaintenanceType, error) { return res, nil } + +// The repo update subcommand. +// `corso repo update-passphrase [...]` +func updatePassphraseCmd() *cobra.Command { + return &cobra.Command{ + Use: updatePassphraseCommand, + Short: "Update the repository passphrase", + Long: `Update the repository passphrase`, + RunE: handleUpdateCmd, + Args: cobra.NoArgs, + Example: providerCommandUpdatePhasephraseExamples, + } +} + +// Handler for calls to `corso repo update-password`. +func handleUpdateCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + // Need to give it a valid service so it won't error out on us even though + // we don't need the graph client. + repos, rdao, err := utils.GetAccountAndConnect(ctx, cmd, path.OneDriveService) + if err != nil { + return Only(ctx, err) + } + + opts := rdao.Opts + + defer utils.CloseRepo(ctx, repos) + + repoID := repos.GetID() + if len(repoID) == 0 { + repoID = events.RepoIDNotFound + } + + r, err := repo.New( + ctx, + rdao.Repo.Account, + rdao.Repo.Storage, + opts, + repoID) + if err != nil { + return Only(ctx, clues.Wrap(err, "Failed to create a repository controller")) + } + + if err := r.UpdatePassword(ctx, flags.NewPhasephraseFV); err != nil { + return Only(ctx, clues.Wrap(err, "Failed to update s3")) + } + + Infof(ctx, "Updated repo password.") + + return nil +} diff --git a/src/cli/repo/repo_test.go b/src/cli/repo/repo_test.go index 97497b719..4c2885076 100644 --- a/src/cli/repo/repo_test.go +++ b/src/cli/repo/repo_test.go @@ -1,14 +1,22 @@ -package repo +package repo_test import ( "testing" + "github.com/alcionai/clues" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/alcionai/corso/src/cli" + "github.com/alcionai/corso/src/cli/config" + "github.com/alcionai/corso/src/cli/repo" + cliTD "github.com/alcionai/corso/src/cli/testdata" "github.com/alcionai/corso/src/internal/tester" + "github.com/alcionai/corso/src/internal/tester/tconfig" + "github.com/alcionai/corso/src/pkg/storage" + storeTD "github.com/alcionai/corso/src/pkg/storage/testdata" ) type RepoUnitSuite struct { @@ -23,7 +31,7 @@ func (suite *RepoUnitSuite) TestAddRepoCommands() { t := suite.T() cmd := &cobra.Command{} - AddCommands(cmd) + repo.AddCommands(cmd) var found bool @@ -32,10 +40,95 @@ func (suite *RepoUnitSuite) TestAddRepoCommands() { require.Len(t, repoCmds, 1) for _, c := range repoCmds[0].Commands() { - if c.Use == maintenanceCommand { + if c.Use == repo.MaintenanceCommand { found = true } } assert.True(t, found, "looking for maintenance command") } + +type RepoE2ESuite struct { + tester.Suite +} + +func TestRepoE2ESuite(t *testing.T) { + suite.Run(t, &RepoE2ESuite{Suite: tester.NewE2ESuite( + t, + [][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs})}) +} + +func (suite *RepoE2ESuite) TestUpdatePassphraseCmd() { + t := suite.T() + ctx, flush := tester.NewContext(t) + + defer flush() + + st := storeTD.NewPrefixedS3Storage(t) + sc, err := st.StorageConfig() + require.NoError(t, err, clues.ToCore(err)) + + cfg := sc.(*storage.S3Config) + + vpr, configFP := tconfig.MakeTempTestConfigClone(t, nil) + + ctx = config.SetViper(ctx, vpr) + + cmd := cliTD.StubRootCmd( + "repo", "init", "s3", + "--config-file", configFP, + "--prefix", cfg.Prefix) + + cli.BuildCommandTree(cmd) + + // run the command + err = cmd.ExecuteContext(ctx) + require.NoError(t, err, clues.ToCore(err)) + + // connect with old passphrase + cmd = cliTD.StubRootCmd( + "repo", "connect", "s3", + "--config-file", configFP, + "--bucket", cfg.Bucket, + "--prefix", cfg.Prefix) + cli.BuildCommandTree(cmd) + + // run the command + err = cmd.ExecuteContext(ctx) + require.NoError(t, err, clues.ToCore(err)) + + cmd = cliTD.StubRootCmd( + "repo", "update-passphrase", + "--config-file", configFP, + "--new-passphrase", "newpass") + cli.BuildCommandTree(cmd) + + // run the command + err = cmd.ExecuteContext(ctx) + require.NoError(t, err, clues.ToCore(err)) + + // connect again with new passphrase + cmd = cliTD.StubRootCmd( + "repo", "connect", "s3", + "--config-file", configFP, + "--bucket", cfg.Bucket, + "--prefix", cfg.Prefix, + "--passphrase", "newpass") + cli.BuildCommandTree(cmd) + + // run the command + err = cmd.ExecuteContext(ctx) + require.NoError(t, err, clues.ToCore(err)) + + // connect with old passphrase - it will fail + cmd = cliTD.StubRootCmd( + "repo", "connect", "s3", + "--config-file", configFP, + "--bucket", cfg.Bucket, + "--prefix", cfg.Prefix) + cli.BuildCommandTree(cmd) + + // run the command + err = cmd.ExecuteContext(ctx) + require.Error(t, err, clues.ToCore(err)) +} diff --git a/src/cli/utils/flags_test.go b/src/cli/utils/flags_test.go index 7abf45a11..1cf1c826d 100644 --- a/src/cli/utils/flags_test.go +++ b/src/cli/utils/flags_test.go @@ -77,7 +77,7 @@ func (suite *FlagUnitSuite) TestAddCorsoPassphraseFlags() { cmd := &cobra.Command{ Use: "test", Run: func(cmd *cobra.Command, args []string) { - assert.Equal(t, "passphrase", flags.CorsoPassphraseFV, flags.CorsoPassphraseFN) + assert.Equal(t, "passphrase", flags.PassphraseFV, flags.PassphraseFN) }, } @@ -85,7 +85,7 @@ func (suite *FlagUnitSuite) TestAddCorsoPassphraseFlags() { // Test arg parsing for few args cmd.SetArgs([]string{ "test", - "--" + flags.CorsoPassphraseFN, "passphrase", + "--" + flags.PassphraseFN, "passphrase", }) err := cmd.Execute() @@ -134,7 +134,7 @@ func (suite *FlagUnitSuite) TestFilesystemFlags() { assert.Equal(t, "tenantID", flags.AzureClientTenantFV, flags.AzureClientTenantFN) assert.Equal(t, "clientID", flags.AzureClientIDFV, flags.AzureClientIDFN) assert.Equal(t, "secret", flags.AzureClientSecretFV, flags.AzureClientSecretFN) - assert.Equal(t, "passphrase", flags.CorsoPassphraseFV, flags.CorsoPassphraseFN) + assert.Equal(t, "passphrase", flags.PassphraseFV, flags.PassphraseFN) }, } @@ -147,7 +147,7 @@ func (suite *FlagUnitSuite) TestFilesystemFlags() { "--" + flags.AzureClientIDFN, "clientID", "--" + flags.AzureClientTenantFN, "tenantID", "--" + flags.AzureClientSecretFN, "secret", - "--" + flags.CorsoPassphraseFN, "passphrase", + "--" + flags.PassphraseFN, "passphrase", }) err := cmd.Execute()