diff --git a/.github/workflows/release-perform.yml b/.github/workflows/release-perform.yml index 633a0db7..9840325d 100644 --- a/.github/workflows/release-perform.yml +++ b/.github/workflows/release-perform.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.24' cache: true - uses: goreleaser/goreleaser-action@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dccd950a..8bcc9fc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Cache Go modules - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.cache/go-build @@ -29,13 +29,13 @@ jobs: ${{ runner.os }}-go- - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.24' id: go - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run build run: make diff --git a/cmd/aem/cli.go b/cmd/aem/cli.go index f1e321ed..4146a643 100644 --- a/cmd/aem/cli.go +++ b/cmd/aem/cli.go @@ -5,6 +5,14 @@ import ( "bytes" "encoding/json" "fmt" + "io" + "os" + "path" + "reflect" + "sort" + "strings" + "time" + "github.com/fatih/color" "github.com/iancoleman/strcase" "github.com/jmespath-community/go-jmespath" @@ -18,13 +26,6 @@ import ( "github.com/wttech/aemc/pkg/common/fmtx" "github.com/wttech/aemc/pkg/common/pathx" "github.com/wttech/aemc/pkg/common/stringsx" - "io" - "os" - "path" - "reflect" - "sort" - "strings" - "time" ) const ( @@ -206,7 +207,7 @@ func (c *CLI) openOutputLogFile() *os.File { } file, err := os.OpenFile(c.outputLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - log.Fatalf(fmt.Sprintf("cannot open/create CLI output file '%s': %s", c.outputLogFile, err)) + log.Fatalf("cannot open/create CLI output file '%s': %s", c.outputLogFile, err) } return file } @@ -231,13 +232,13 @@ func (c *CLI) printCommandResult() { if c.outputNoColor { entry := log.WithField("changed", r.Changed).WithField("elapsed", r.Elapsed) if r.Failed { - entry.Errorf(msg) + entry.Error(msg) } else { - entry.Infof(msg) + entry.Info(msg) } } else { if r.Failed { - log.Errorf(color.RedString(msg)) + log.Error(color.RedString(msg)) } else { if r.Changed { log.Info(color.YellowString(msg)) diff --git a/cmd/aem/user.go b/cmd/aem/user.go index 20c5211a..0a9897d8 100644 --- a/cmd/aem/user.go +++ b/cmd/aem/user.go @@ -13,6 +13,7 @@ func (c *CLI) userCmd() *cobra.Command { Aliases: []string{"usr"}, } cmd.AddCommand(c.userKeyStore()) + cmd.AddCommand(c.userKey()) cmd.AddCommand(c.userPassword()) return cmd } @@ -29,10 +30,21 @@ func (c *CLI) userKeyStore() *cobra.Command { return cmd } +func (c *CLI) userKey() *cobra.Command { + cmd := &cobra.Command{ + Use: "key", + Short: "Private keys management", + Aliases: []string{"keys"}, + } + cmd.AddCommand(c.userKeyAdd()) + cmd.AddCommand(c.userKeyDelete()) + return cmd +} + func (c *CLI) userPassword() *cobra.Command { cmd := &cobra.Command{ Use: "password", - Short: "User password management", + Short: "Password management", Aliases: []string{"pwd"}, } cmd.AddCommand(c.UserPasswordSet()) @@ -42,7 +54,7 @@ func (c *CLI) userPassword() *cobra.Command { func (c *CLI) KeystoreStatus() *cobra.Command { cmd := &cobra.Command{ Use: "status", - Short: "Get status of keystore", + Short: "Get status of a user keystore", Aliases: []string{"show", "get", "read", "describe", "ls"}, Run: func(cmd *cobra.Command, args []string) { instance, err := c.aem.InstanceManager().One() @@ -54,7 +66,7 @@ func (c *CLI) KeystoreStatus() *cobra.Command { id, _ := cmd.Flags().GetString("id") scope, _ := cmd.Flags().GetString("scope") - result, err := instance.Auth().UserManager().KeystoreStatus(scope, id) + result, err := instance.Auth().UserManager().Keystore().Status(scope, id) if err != nil { c.Error(err) @@ -68,14 +80,13 @@ func (c *CLI) KeystoreStatus() *cobra.Command { cmd.Flags().String("id", "", "user id") _ = cmd.MarkFlagRequired("id") cmd.Flags().String("scope", "", "user scope") - _ = cmd.MarkFlagRequired("scope") return cmd } func (c *CLI) KeystoreCreate() *cobra.Command { cmd := &cobra.Command{ Use: "create", - Short: "Create user Keystore", + Short: "Create user keystore", Aliases: []string{"make", "new"}, Run: func(cmd *cobra.Command, args []string) { instance, err := c.aem.InstanceManager().One() @@ -87,7 +98,7 @@ func (c *CLI) KeystoreCreate() *cobra.Command { id, _ := cmd.Flags().GetString("id") scope, _ := cmd.Flags().GetString("scope") password, _ := cmd.Flags().GetString("keystore-password") - changed, err := instance.Auth().UserManager().KeystoreCreate(scope, id, password) + changed, err := instance.Auth().UserManager().Keystore().Create(scope, id, password) if err != nil { c.Error(err) @@ -95,9 +106,9 @@ func (c *CLI) KeystoreCreate() *cobra.Command { } if changed { - c.Changed("User Keystore created") + c.Changed("User keystore created") } else { - c.Ok("User Keystore already exists") + c.Ok("User keystore already exists") } }, } @@ -110,6 +121,94 @@ func (c *CLI) KeystoreCreate() *cobra.Command { return cmd } +func (c *CLI) userKeyAdd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add", + Short: "Add user private key to the keystore", + Aliases: []string{"create", "new"}, + Run: func(cmd *cobra.Command, args []string) { + instance, err := c.aem.InstanceManager().One() + if err != nil { + c.Error(err) + return + } + + changed, err := instance.Auth().UserManager().Keystore().AddKey( + cmd.Flag("scope").Value.String(), + cmd.Flag("id").Value.String(), + cmd.Flag("keystore-file").Value.String(), + cmd.Flag("keystore-password").Value.String(), + cmd.Flag("key-alias").Value.String(), + cmd.Flag("key-password").Value.String(), + cmd.Flag("new-alias").Value.String(), + ) + + if err != nil { + c.Error(err) + return + } + if changed { + c.Changed("User key added") + } else { + c.Ok("User key already exists") + } + }, + } + + cmd.Flags().String("id", "", "user id") + _ = cmd.MarkFlagRequired("id") + cmd.Flags().String("scope", "", "user scope") + cmd.Flags().String("keystore-file", "", "path to keystore file") + _ = cmd.MarkFlagRequired("keystore-file") + cmd.Flags().String("keystore-password", "", "keystore password") + _ = cmd.MarkFlagRequired("keystore-password") + cmd.Flags().String("key-alias", "", "key alias") + _ = cmd.MarkFlagRequired("key-alias") + cmd.Flags().String("key-password", "", "key password") + cmd.Flags().String("new-alias", "", "new key alias (optional)") + + return cmd +} + +func (c *CLI) userKeyDelete() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete user private key from the keystore", + Aliases: []string{"remove", "rm"}, + Run: func(cmd *cobra.Command, args []string) { + instance, err := c.aem.InstanceManager().One() + if err != nil { + c.Error(err) + return + } + + changed, err := instance.Auth().UserManager().Keystore().DeleteKey( + cmd.Flag("scope").Value.String(), + cmd.Flag("id").Value.String(), + cmd.Flag("key-alias").Value.String(), + ) + + if err != nil { + c.Error(err) + return + } + if changed { + c.Changed("User key deleted") + } else { + c.Ok("User key does not exist") + } + }, + } + + cmd.Flags().String("id", "", "user id") + _ = cmd.MarkFlagRequired("id") + cmd.Flags().String("scope", "", "user scope") + cmd.Flags().String("key-alias", "", "key alias") + _ = cmd.MarkFlagRequired("key-alias") + + return cmd +} + func (c *CLI) UserPasswordSet() *cobra.Command { cmd := &cobra.Command{ Use: "set", diff --git a/go.mod b/go.mod index e1eca5b0..82af4845 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/wttech/aemc -go 1.20 +go 1.24 require ( github.com/Masterminds/goutils v1.1.1 @@ -23,6 +23,7 @@ require ( github.com/mholt/archiver/v3 v3.5.1 github.com/olekukonko/tablewriter v0.0.5 github.com/otiai10/copy v1.14.0 + github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/samber/lo v1.39.0 github.com/segmentio/textio v1.2.0 diff --git a/go.sum b/go.sum index e47b84bf..6823638b 100644 --- a/go.sum +++ b/go.sum @@ -33,11 +33,13 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/essentialkaos/check v1.2.1 h1:avvyFy/1acUNwfxwuOLsHeCjfXtMygtbu0lVDr3nxFs= +github.com/essentialkaos/check v1.2.1/go.mod h1:PhxzfJWlf5L/skuyhzBLIvjMB5Xu9TIyDIsqpY5MvB8= github.com/essentialkaos/go-jar v1.0.6 h1:BAzvYTsXurNd/FcL4nYSH2pWV9DfavVAzlyYqULxcPE= github.com/essentialkaos/go-jar v1.0.6/go.mod h1:HZPc/zrzjoE2FjCRmIXZj7zQ6RN33/qjtf6ZxaEy7Hw= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= @@ -79,7 +81,9 @@ github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -95,6 +99,7 @@ github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssn github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -107,6 +112,9 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -119,6 +127,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= @@ -218,6 +227,7 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -226,6 +236,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/keystore/keystore_private_key.go b/pkg/keystore/private_key.go similarity index 100% rename from pkg/keystore/keystore_private_key.go rename to pkg/keystore/private_key.go diff --git a/pkg/keystore/keystore_status.go b/pkg/keystore/status.go similarity index 84% rename from pkg/keystore/keystore_status.go rename to pkg/keystore/status.go index 90172a14..3ea71b9d 100644 --- a/pkg/keystore/keystore_status.go +++ b/pkg/keystore/status.go @@ -2,9 +2,10 @@ package keystore import ( "bytes" + "io" + "github.com/samber/lo" "github.com/wttech/aemc/pkg/common/fmtx" - "io" ) type Status struct { @@ -34,3 +35,9 @@ func (s *Status) MarshalText() string { }))) return bs.String() } + +func (s *Status) HasAlias(privateKeyAlias string) bool { + return lo.ContainsBy(s.PrivateKeys, func(c PrivateKey) bool { + return c.Alias == privateKeyAlias + }) +} diff --git a/pkg/keystore_manager.go b/pkg/keystore_manager.go new file mode 100644 index 00000000..2fd12a3c --- /dev/null +++ b/pkg/keystore_manager.go @@ -0,0 +1,185 @@ +package pkg + +import ( + "fmt" + "os" + "slices" + + jks "github.com/pavlo-v-chernykh/keystore-go/v4" + "github.com/wttech/aemc/pkg/common/pathx" + "github.com/wttech/aemc/pkg/keystore" +) + +type KeystoreManager struct { + instance *Instance +} + +func (km *KeystoreManager) Status(scope, id string) (*keystore.Status, error) { + userKeystorePath := composeKeystoreStatusPath(scope, id) + + response, err := km.instance.http.Request().Get(userKeystorePath) + + if err != nil { + return nil, fmt.Errorf("%s > cannot read user keystore: %w", km.instance.IDColor(), err) + } + + if response.IsError() { + return nil, fmt.Errorf("%s > cannot read user keystore: %s", km.instance.IDColor(), response.Status()) + } + + result, err := keystore.UnmarshalStatus(response.RawBody()) + if err != nil { + return nil, fmt.Errorf("%s > cannot parse user keystore status response: %w", km.instance.IDColor(), err) + } + + return result, nil +} + +func (km *KeystoreManager) Create(scope, id, keystorePassword string) (bool, error) { + statusResponse, statusError := km.Status(scope, id) + if statusError != nil { + return false, statusError + } + + if statusResponse.Created { + return false, nil + } + + pathParams := map[string]string{ + "newPassword": keystorePassword, + "rePassword": keystorePassword, + ":operation": "createStore", + } + + userKeystoreCreatePath := composeKeystoreOperationsPath(scope, id) + postResponse, postError := km.instance.http.Request().SetQueryParams(pathParams).Post(userKeystoreCreatePath) + + if postError != nil { + return false, fmt.Errorf("%s > cannot create user keystore: %w", km.instance.IDColor(), postError) + } + + if postResponse.IsError() { + return false, fmt.Errorf("%s > cannot create user keystore: %s", km.instance.IDColor(), postResponse.Status()) + } + + return true, nil +} + +func (km *KeystoreManager) AddKey(scope, id, keystoreFilePath, keystoreFilePassword, privateKeyAlias, privateKeyPassword, privateKeyNewAlias string) (bool, error) { + if !pathx.Exists(keystoreFilePath) { + return false, fmt.Errorf("%s > keystore file does not exist: %s", km.instance.IDColor(), keystoreFilePath) + } + if privateKeyNewAlias == "" { + privateKeyNewAlias = privateKeyAlias + } + if privateKeyPassword == "" { + privateKeyPassword = keystoreFilePassword + } + + readKeystore, err := readKeyStore(keystoreFilePath, []byte(keystoreFilePassword)) + if err != nil { + return false, fmt.Errorf("%s > cannot read keystore file %s: %w", km.instance.IDColor(), keystoreFilePath, err) + } + + aliases := readKeystore.Aliases() + if aliases == nil { + return false, fmt.Errorf("%s > keystore file does not contain any aliases", km.instance.IDColor()) + } + if !slices.Contains(aliases, privateKeyAlias) { + return false, fmt.Errorf("%s > keystore file does not contain alias: %s", km.instance.IDColor(), privateKeyAlias) + } + + status, err := km.Status(scope, id) + if err != nil { + return false, err + } + + if status == nil || !status.Created { + return false, fmt.Errorf("%s > cannot add key as keystore does not exist", km.instance.IDColor()) + } + if status.HasAlias(privateKeyAlias) { + return false, nil + } + + requestFiles := map[string]string{ + "keyStore": keystoreFilePath, + } + + keystorePath := composeKeystoreOperationsPath(scope, id) + formData := map[string]string{ + "keyStorePass": keystoreFilePassword, + "alias": privateKeyAlias, + "keyPassword": privateKeyPassword, + "newAlias": privateKeyNewAlias, + "keyStoreType": "jks", + } + + response, err := km.instance.http.Request(). + SetFiles(requestFiles). + SetFormData(formData). + Post(keystorePath) + + if err != nil { + return false, fmt.Errorf("%s > cannot add key: %w", km.instance.IDColor(), err) + } + if response.IsError() { + return false, fmt.Errorf("%s > cannot add key: %s", km.instance.IDColor(), response.Status()) + } + return true, nil +} + +func (km *KeystoreManager) DeleteKey(scope, id, privateKeyAlias string) (bool, error) { + status, err := km.Status(scope, id) + if err != nil { + return false, err + } + + if status == nil || !status.Created { + return false, fmt.Errorf("%s > cannot delete key: keystore does not exist", km.instance.IDColor()) + } + if !status.HasAlias(privateKeyAlias) { + return false, nil + } + + formData := map[string]string{ + "removeAlias": privateKeyAlias, + } + + userKeystorePath := composeKeystoreOperationsPath(scope, id) + response, err := km.instance.http.Request(). + SetFormData(formData). + Post(userKeystorePath) + + if err != nil { + return false, fmt.Errorf("%s > cannot delete key: %w", km.instance.IDColor(), err) + } + if response.IsError() { + return false, fmt.Errorf("%s > cannot delete key: %s", km.instance.IDColor(), response.Status()) + } + + return true, nil +} + +func composeKeystoreStatusPath(scope, id string) string { + return composeUserPath(scope, id) + ".ks.json" +} + +func composeKeystoreOperationsPath(scope, id string) string { + return composeUserPath(scope, id) + ".ks.html" +} + +func readKeyStore(filename string, password []byte) (*jks.KeyStore, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + defer f.Close() + + ks := jks.New() + if err := ks.Load(f, password); err != nil { + return nil, err + } + + return &ks, nil +} diff --git a/pkg/project.go b/pkg/project.go index 9002df8f..d0dec35d 100644 --- a/pkg/project.go +++ b/pkg/project.go @@ -3,6 +3,9 @@ package pkg import ( "embed" "fmt" + "io/fs" + "strings" + "github.com/magiconair/properties" log "github.com/sirupsen/logrus" "github.com/wttech/aemc/pkg/common" @@ -10,8 +13,6 @@ import ( "github.com/wttech/aemc/pkg/common/osx" "github.com/wttech/aemc/pkg/common/pathx" "github.com/wttech/aemc/pkg/project" - "io/fs" - "strings" ) type Project struct { @@ -210,7 +211,7 @@ func (p Project) DirsIgnored() []string { } func (p Project) ScaffoldGettingStarted() string { - text := fmt.Sprintf(strings.Join([]string{ + text := fmt.Sprint(strings.Join([]string{ "AEM Compose project now contains required files.", "", "Consider saving the project to VCS repository.", @@ -222,7 +223,7 @@ func (p Project) ScaffoldGettingStarted() string { } func (p Project) InitGettingStartedError() string { - text := fmt.Sprintf(strings.Join([]string{ + text := fmt.Sprint(strings.Join([]string{ "AEM Compose project is not yet ready to use!", "", "Be sure to provide AEM files (SDK ZIP or Quickstart JAR + License + Service Packs) to directory '" + p.aem.BaseOpts().LibDir + "'.", @@ -231,7 +232,7 @@ func (p Project) InitGettingStartedError() string { } func (p Project) InitGettingStartedSuccess() string { - text := fmt.Sprintf(strings.Join([]string{ + text := fmt.Sprint(strings.Join([]string{ "AEM Compose project is ready to use!", "", fmt.Sprintf("Consider excluding the directories from VCS versioning and IDE indexing: %s", strings.Join(p.DirsIgnored(), ", ")), diff --git a/pkg/user_manager.go b/pkg/user_manager.go index 12f3b7ab..d2722078 100644 --- a/pkg/user_manager.go +++ b/pkg/user_manager.go @@ -3,7 +3,6 @@ package pkg import ( "fmt" - "github.com/wttech/aemc/pkg/keystore" "github.com/wttech/aemc/pkg/user" ) @@ -19,61 +18,12 @@ const ( UsersPath = "/home/users" ) -func (um *UserManager) KeystoreStatus(scope, id string) (*keystore.Status, error) { - userKeystorePath := assembleUserPath(scope, id) + ".ks.json" - - response, err := um.instance.http.Request().Get(userKeystorePath) - - if err != nil { - return nil, fmt.Errorf("%s > cannot read user Keystore: %w", um.instance.IDColor(), err) - } - - if response.IsError() { - return nil, fmt.Errorf("%s > cannot read user keystore: %s", um.instance.IDColor(), response.Status()) - } - - result, err := keystore.UnmarshalStatus(response.RawBody()) - - if err != nil { - return nil, fmt.Errorf("%s > cannot parse user Keystore status response: %w", um.instance.IDColor(), err) - } - - return result, nil -} - -func (um *UserManager) KeystoreCreate(scope, id, keystorePassword string) (bool, error) { - statusResponse, statusError := um.KeystoreStatus(scope, id) - - if statusError != nil { - return false, statusError - } - - if statusResponse.Created { - return false, nil - } - - pathParams := map[string]string{ - "newPassword": keystorePassword, - "rePassword": keystorePassword, - ":operation": "createStore", - } - - userKeystoreCreatePath := assembleUserPath(scope, id) + ".ks.html" - postResponse, postError := um.instance.http.Request().SetQueryParams(pathParams).Post(userKeystoreCreatePath) - - if postError != nil { - return false, fmt.Errorf("%s > cannot create user keystore: %w", um.instance.IDColor(), postError) - } - - if postResponse.IsError() { - return false, fmt.Errorf("%s > cannot create user keystore: %s", um.instance.IDColor(), postResponse.Status()) - } - - return true, nil +func (um *UserManager) Keystore() *KeystoreManager { + return &KeystoreManager{instance: um.instance} } func (um *UserManager) ReadState(scope string, id string) (*user.Status, error) { - userPath := assembleUserPath(scope, id) + userPath := composeUserPath(scope, id) response, err := um.instance.http.Request().Get(userPath + ".json") @@ -85,7 +35,6 @@ func (um *UserManager) ReadState(scope string, id string) (*user.Status, error) } result, err := user.UnmarshalStatus(response.RawBody()) - if err != nil { return nil, fmt.Errorf("%s > cannot parse user status response: %w", um.instance.IDColor(), err) } @@ -95,13 +44,11 @@ func (um *UserManager) ReadState(scope string, id string) (*user.Status, error) 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) - + userPath := composeUserPath(scope, id) passwordCheckResponse, err := um.instance.http.Request(). SetBasicAuth(userStatus.AuthorizableID, password). Get(userPath + ".json") @@ -118,7 +65,6 @@ func (um *UserManager) SetPassword(scope string, id string, password string) (bo } 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) } @@ -129,7 +75,7 @@ func (um *UserManager) SetPassword(scope string, id string, password string) (bo return true, nil } -func assembleUserPath(scope string, id string) string { +func composeUserPath(scope string, id string) string { if scope == "" { return UsersPath + "/" + id }