diff --git a/cmd/aem/user.go b/cmd/aem/user.go index fcd9c7f6..20c5211a 100644 --- a/cmd/aem/user.go +++ b/cmd/aem/user.go @@ -1,6 +1,10 @@ package main -import "github.com/spf13/cobra" +import ( + "fmt" + + "github.com/spf13/cobra" +) func (c *CLI) userCmd() *cobra.Command { cmd := &cobra.Command{ @@ -9,6 +13,7 @@ func (c *CLI) userCmd() *cobra.Command { Aliases: []string{"usr"}, } cmd.AddCommand(c.userKeyStore()) + cmd.AddCommand(c.userPassword()) return cmd } @@ -24,6 +29,16 @@ func (c *CLI) userKeyStore() *cobra.Command { return cmd } +func (c *CLI) userPassword() *cobra.Command { + cmd := &cobra.Command{ + Use: "password", + Short: "User password management", + Aliases: []string{"pwd"}, + } + cmd.AddCommand(c.UserPasswordSet()) + return cmd +} + func (c *CLI) KeystoreStatus() *cobra.Command { cmd := &cobra.Command{ Use: "status", @@ -90,8 +105,49 @@ func (c *CLI) KeystoreCreate() *cobra.Command { cmd.Flags().String("id", "", "user id") _ = cmd.MarkFlagRequired("id") cmd.Flags().String("scope", "", "user scope") - _ = cmd.MarkFlagRequired("scope") cmd.Flags().String("keystore-password", "", "keystore password") _ = cmd.MarkFlagRequired("keystore-password") return cmd } + +func (c *CLI) UserPasswordSet() *cobra.Command { + cmd := &cobra.Command{ + Use: "set", + Short: "Set user password. Password is read from input.", + Aliases: []string{"update", "change"}, + Run: func(cmd *cobra.Command, args []string) { + instances, err := c.aem.InstanceManager().One() + if err != nil { + c.Error(err) + return + } + + id, _ := cmd.Flags().GetString("id") + scope, _ := cmd.Flags().GetString("scope") + + var password string + if err := c.ReadInput(&password); err != nil { + c.Fail(fmt.Sprintf("error reading password from input: %s", err)) + return + } + + changed, err := instances.Auth().UserManager().SetPassword(scope, id, password) + if err != nil { + c.Error(err) + return + } + + if changed { + c.Changed("User password changed") + } else { + c.Ok("User password already set") + } + }, + } + + cmd.Flags().String("id", "", "user id") + _ = cmd.MarkFlagRequired("id") + cmd.Flags().String("scope", "", "user scope") + + return cmd +} diff --git a/pkg/user/status.go b/pkg/user/status.go new file mode 100644 index 00000000..88f6033c --- /dev/null +++ b/pkg/user/status.go @@ -0,0 +1,44 @@ +package user + +import ( + "fmt" + "io" + + "github.com/wttech/aemc/pkg/common/fmtx" +) + +const ( + // Repository user types (as stored in JCR) + RepUserType = "rep:User" + RepSystemUserType = "rep:SystemUser" + + // Maped user types + UserType = "user" + SystemUserType = "systemUser" +) + +type Status struct { + Type string `json:"jcr:primaryType"` + AuthorizableID string `json:"rep:authorizableId"` +} + +func UnmarshalStatus(readCloser io.ReadCloser) (*Status, error) { + var status = Status{ + Type: "rep:User", + AuthorizableID: "", + } + if err := fmtx.UnmarshalJSON(readCloser, &status); err != nil { + return nil, err + } + + switch status.Type { + case RepUserType: + status.Type = UserType + case RepSystemUserType: + status.Type = SystemUserType + default: + return nil, fmt.Errorf("unknown user type: %s", status.Type) + } + + return &status, nil +} diff --git a/pkg/user_manager.go b/pkg/user_manager.go index 81d8bfc0..12f3b7ab 100644 --- a/pkg/user_manager.go +++ b/pkg/user_manager.go @@ -2,7 +2,9 @@ package pkg import ( "fmt" + "github.com/wttech/aemc/pkg/keystore" + "github.com/wttech/aemc/pkg/user" ) type UserManager struct { @@ -18,7 +20,7 @@ const ( ) func (um *UserManager) KeystoreStatus(scope, id string) (*keystore.Status, error) { - userKeystorePath := UsersPath + "/" + scope + "/" + id + ".ks.json" + userKeystorePath := assembleUserPath(scope, id) + ".ks.json" response, err := um.instance.http.Request().Get(userKeystorePath) @@ -46,7 +48,7 @@ func (um *UserManager) KeystoreCreate(scope, id, keystorePassword string) (bool, return false, statusError } - if statusResponse.Created == true { + if statusResponse.Created { return false, nil } @@ -56,7 +58,7 @@ func (um *UserManager) KeystoreCreate(scope, id, keystorePassword string) (bool, ":operation": "createStore", } - userKeystoreCreatePath := UsersPath + "/" + scope + "/" + id + ".ks.html" + userKeystoreCreatePath := assembleUserPath(scope, id) + ".ks.html" postResponse, postError := um.instance.http.Request().SetQueryParams(pathParams).Post(userKeystoreCreatePath) if postError != nil { @@ -69,3 +71,67 @@ func (um *UserManager) KeystoreCreate(scope, id, keystorePassword string) (bool, return true, nil } + +func (um *UserManager) ReadState(scope string, id string) (*user.Status, error) { + userPath := assembleUserPath(scope, id) + + response, err := um.instance.http.Request().Get(userPath + ".json") + + if err != nil { + return nil, fmt.Errorf("%s > cannot read user: %w", um.instance.IDColor(), err) + } + if response.IsError() { + return nil, fmt.Errorf("%s > cannot read user: %s", um.instance.IDColor(), response.Status()) + } + + result, err := user.UnmarshalStatus(response.RawBody()) + + if err != nil { + return nil, fmt.Errorf("%s > cannot parse user status response: %w", um.instance.IDColor(), err) + } + + return result, nil +} + +func (um *UserManager) SetPassword(scope string, id string, password string) (bool, error) { + userStatus, err := um.ReadState(scope, id) + + if err != nil { + return false, err + } + + userPath := assembleUserPath(scope, id) + + passwordCheckResponse, err := um.instance.http.Request(). + SetBasicAuth(userStatus.AuthorizableID, password). + Get(userPath + ".json") + + if err != nil { + return false, fmt.Errorf("%s > cannot check user password: %w", um.instance.IDColor(), err) + } + if !passwordCheckResponse.IsError() { + return false, nil + } + + props := map[string]any{ + "rep:password": password, + } + + postResponse, err := um.instance.http.RequestFormData(props).Post(userPath) + + if err != nil { + return false, fmt.Errorf("%s > cannot set user password: %w", um.instance.IDColor(), err) + } + if postResponse.IsError() { + return false, fmt.Errorf("%s > cannot set user password: %s", um.instance.IDColor(), postResponse.Status()) + } + + return true, nil +} + +func assembleUserPath(scope string, id string) string { + if scope == "" { + return UsersPath + "/" + id + } + return UsersPath + "/" + scope + "/" + id +}