diff --git a/cmd/cmd_utils.go b/cmd/cmd_utils.go index 90c5ac711..6936f0435 100644 --- a/cmd/cmd_utils.go +++ b/cmd/cmd_utils.go @@ -19,13 +19,16 @@ package main import ( + "fmt" "net/url" + "os" "strings" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/pelicanplatform/pelican/config" "github.com/pelicanplatform/pelican/param" ) @@ -58,3 +61,22 @@ func getPreferredCaches() ([]*url.URL, error) { return caches, nil } + +const ( + incorrectPasswordAccessMessage = "Failed to access local credential file - entered incorrect local decryption password" + incorrectPasswordResetMessage = "Failed to reset password - entered incorrect local decryption password" +) + +func handleIncorrectPassword(err error, actionMessage string) bool { + if err == nil || !errors.Is(err, config.ErrIncorrectPassword) { + return false + } + fmt.Fprintln(os.Stderr, actionMessage) + fmt.Fprintln(os.Stderr, "If you have forgotten your password, you can reset the local state (deleting all on-disk credentials)") + fmt.Fprintf(os.Stderr, "by running '%s credentials reset-local'\n", os.Args[0]) + return true +} + +func handleCredentialPasswordError(err error) bool { + return handleIncorrectPassword(err, incorrectPasswordAccessMessage) +} diff --git a/cmd/config_mgr.go b/cmd/config_mgr.go index 4730396d9..86153d788 100644 --- a/cmd/config_mgr.go +++ b/cmd/config_mgr.go @@ -101,12 +101,26 @@ func addConfigSubcommands(configCmd *cobra.Command) { Run: func(cmd *cobra.Command, args []string) { err := config.ResetPassword() if err != nil { - fmt.Fprintln(os.Stderr, "Failed to get reset password:", err) + if handleIncorrectPassword(err, incorrectPasswordResetMessage) { + os.Exit(1) + } + fmt.Fprintln(os.Stderr, "Failed to reset password:", err) os.Exit(1) } }, }) + configCmd.AddCommand(&cobra.Command{ + Use: "reset-local", + Short: "Delete all local credentials for the current user", + Long: "Delete all local credentials for the current user", + Run: func(cmd *cobra.Command, args []string) { + if err := config.DeleteCredentials(); err != nil { + fmt.Fprintln(os.Stderr, "Failed to delete local credentials:", err) + os.Exit(1) + } + }, + }) } func printOauthConfig() { diff --git a/cmd/object_copy.go b/cmd/object_copy.go index 0090b3571..00b216bb2 100644 --- a/cmd/object_copy.go +++ b/cmd/object_copy.go @@ -174,6 +174,9 @@ func copyMain(cmd *cobra.Command, args []string) { // Exit with failure if result != nil { + if handleCredentialPasswordError(result) { + os.Exit(1) + } // Print the list of errors errMsg := result.Error() var te *client.TransferErrors diff --git a/cmd/object_delete.go b/cmd/object_delete.go index 9ce99356a..474b8269c 100644 --- a/cmd/object_delete.go +++ b/cmd/object_delete.go @@ -80,6 +80,9 @@ func deleteMain(cmd *cobra.Command, args []string) error { err = client.DoDelete(ctx, remoteDestination, isRecursive, client.WithTokenLocation(tokenLocation)) if err != nil { + if handleCredentialPasswordError(err) { + os.Exit(1) + } log.Errorf("Failure deleting %s: %v", remoteDestination, err.Error()) os.Exit(1) } diff --git a/cmd/object_get.go b/cmd/object_get.go index 485b735ee..7d5784acd 100644 --- a/cmd/object_get.go +++ b/cmd/object_get.go @@ -130,6 +130,9 @@ func getMain(cmd *cobra.Command, args []string) { // Exit with failure if attemptErr != nil { // Print the list of errors + if handleCredentialPasswordError(attemptErr) { + os.Exit(1) + } errMsg := attemptErr.Error() var pe error_codes.PelicanError var te *client.TransferErrors diff --git a/cmd/object_ls.go b/cmd/object_ls.go index e718d9e68..a2bfa7255 100644 --- a/cmd/object_ls.go +++ b/cmd/object_ls.go @@ -97,6 +97,9 @@ func listMain(cmd *cobra.Command, args []string) error { // Exit with failure if err != nil { + if handleCredentialPasswordError(err) { + os.Exit(1) + } // Print the list of errors errMsg := err.Error() var te *client.TransferErrors diff --git a/cmd/object_prestage.go b/cmd/object_prestage.go index 7757a52f1..e3f321286 100644 --- a/cmd/object_prestage.go +++ b/cmd/object_prestage.go @@ -112,6 +112,9 @@ func prestageMain(cmd *cobra.Command, args []string) { // Exit with failure if err != nil { // Print the list of errors + if handleCredentialPasswordError(err) { + os.Exit(1) + } errMsg := err.Error() var pe error_codes.PelicanError var te *client.TransferErrors diff --git a/cmd/object_put.go b/cmd/object_put.go index 2c4aec4bc..8d67bd34b 100644 --- a/cmd/object_put.go +++ b/cmd/object_put.go @@ -245,6 +245,9 @@ func putMain(cmd *cobra.Command, args []string) { // Exit with failure if result != nil { + if handleCredentialPasswordError(result) { + os.Exit(1) + } // Print the list of errors errMsg := result.Error() var te *client.TransferErrors diff --git a/cmd/object_stat.go b/cmd/object_stat.go index 24de8e7e9..fdb430653 100644 --- a/cmd/object_stat.go +++ b/cmd/object_stat.go @@ -106,6 +106,9 @@ func statMain(cmd *cobra.Command, args []string) { // Exit with failure if err != nil { + if handleCredentialPasswordError(err) { + os.Exit(1) + } // Print the list of errors errMsg := err.Error() var te *client.TransferErrors diff --git a/cmd/object_sync.go b/cmd/object_sync.go index a0db6bdfa..ced91123d 100644 --- a/cmd/object_sync.go +++ b/cmd/object_sync.go @@ -196,6 +196,9 @@ func syncMain(cmd *cobra.Command, args []string) { // Exit with failure if err != nil { + if handleCredentialPasswordError(err) { + os.Exit(1) + } // Print the list of errors errMsg := err.Error() var pe error_codes.PelicanError diff --git a/config/encrypted.go b/config/encrypted.go index 6bea56804..afb961a84 100644 --- a/config/encrypted.go +++ b/config/encrypted.go @@ -33,6 +33,7 @@ import ( "io" "os" "path/filepath" + "strings" log "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -48,6 +49,8 @@ import ( // the password again later. var setEmptyPassword = false +var ErrIncorrectPassword = errors.New("incorrect password") + func GetEncryptedConfigName() (string, error) { configDir := viper.GetString("ConfigDir") if GetPreferredPrefix() == PelicanPrefix || IsRootExecution() { @@ -79,6 +82,18 @@ func EncryptedConfigExists() (bool, error) { return true, nil } +// Delete the user's local credential file +func DeleteCredentials() error { + filename, err := GetEncryptedConfigName() + if err != nil { + return err + } + if err = os.Remove(filename); errors.Is(err, os.ErrNotExist) { + return nil + } + return err +} + // Return the PEM-formatted contents of the encrypted configuration file func GetEncryptedContents() (string, error) { filename, err := GetEncryptedConfigName() @@ -242,6 +257,9 @@ func GetCredentialConfigContents() (OSDFConfig, error) { return config, errors.New("Encrypted key present; must have non-empty password") } if key, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes, password); err != nil { + if strings.Contains(err.Error(), "pkcs8: incorrect password") { + err = ErrIncorrectPassword + } return config, err } if typedPassword {