diff --git a/README.md b/README.md index 6b9f030..5652c8a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,12 @@ imscli authorize user help The complete documentation of the project is available in the [DOCUMENTATION.md](DOCUMENTATION.md) file. +## Development Notes + +### PersistentPreRunE and subcommands + +The root command defines a `PersistentPreRunE` that loads configuration from flags, environment variables, and config files (see `cmd/root.go`). In cobra, if a subcommand defines its own `PersistentPreRunE`, it **overrides** the parent's — the root's `PersistentPreRunE` will not run for that subcommand or its children. If you need to add a `PersistentPreRunE` to a subcommand, you must explicitly call the parent's first. + ## Contributing Contributions are welcomed! Read the [Contributing Guide](CONTRIBUTING.md) for more information. diff --git a/cmd/admin/organizations.go b/cmd/admin/organizations.go index 5318d35..8e2759b 100644 --- a/cmd/admin/organizations.go +++ b/cmd/admin/organizations.go @@ -38,7 +38,7 @@ func OrganizationsCmd(imsConfig *ims.Config) *cobra.Command { }, } cmd.Flags().StringVarP(&imsConfig.Guid, "guid", "g", "", "User ID.") - cmd.Flags().StringVarP(&imsConfig.AuthSrc, "authSrc", "s", "", "Authorization source.") + cmd.Flags().StringVarP(&imsConfig.AuthSrc, "authSrc", "A", "", "Authorization source.") cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS client ID.") cmd.Flags().StringVarP(&imsConfig.ServiceToken, "serviceToken", "t", "", "Service token.") cmd.Flags().StringVarP(&imsConfig.OrgsAPIVersion, "orgsApiVersion", "a", "v5", "Admin organizations API version.") diff --git a/cmd/admin/profile.go b/cmd/admin/profile.go index bcce154..9904ad8 100644 --- a/cmd/admin/profile.go +++ b/cmd/admin/profile.go @@ -36,7 +36,7 @@ func ProfileCmd(imsConfig *ims.Config) *cobra.Command { }, } cmd.Flags().StringVarP(&imsConfig.Guid, "guid", "g", "", "User ID.") - cmd.Flags().StringVarP(&imsConfig.AuthSrc, "authSrc", "s", "", "Authorization source.") + cmd.Flags().StringVarP(&imsConfig.AuthSrc, "authSrc", "A", "", "Authorization source.") cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS client ID.") cmd.Flags().StringVarP(&imsConfig.ServiceToken, "serviceToken", "t", "", "Service token.") cmd.Flags().StringVarP(&imsConfig.ProfileAPIVersion, "profileApiVersion", "a", "v1", "Admin profile API version.") diff --git a/cmd/authz/jwt.go b/cmd/authz/jwt.go index 9ab54d2..90898c7 100644 --- a/cmd/authz/jwt.go +++ b/cmd/authz/jwt.go @@ -36,9 +36,9 @@ func JWTCmd(imsConfig *ims.Config) *cobra.Command { } cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - cmd.Flags().StringVarP(&imsConfig.ClientSecret, "clientSecret", "s", "", "IMS Client secret.") + cmd.Flags().StringVarP(&imsConfig.ClientSecret, "clientSecret", "p", "", "IMS Client secret.") cmd.Flags().StringVarP(&imsConfig.Organization, "organization", "o", "", "IMS Organization.") - cmd.Flags().StringVarP(&imsConfig.Account, "account", "a", "", "Technical Account ID.") + cmd.Flags().StringVarP(&imsConfig.Account, "account", "A", "", "Technical Account ID.") cmd.Flags().StringVarP(&imsConfig.PrivateKeyPath, "privateKey", "k", "", "Private Key file.") cmd.Flags().StringSliceVarP(&imsConfig.Metascopes, "metascopes", "m", []string{}, "Metascopes to request.") diff --git a/cmd/authz/pkce.go b/cmd/authz/pkce.go index 463e286..36d4e76 100644 --- a/cmd/authz/pkce.go +++ b/cmd/authz/pkce.go @@ -27,8 +27,7 @@ func UserPkceCmd(imsConfig *ims.Config) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true - imsConfig.PKCE = true - resp, err := imsConfig.AuthorizeUser() + resp, err := imsConfig.AuthorizeUserPKCE() if err != nil { return fmt.Errorf("error in user authorization: %w", err) } diff --git a/cmd/authz/service.go b/cmd/authz/service.go index 391e58c..c47e272 100644 --- a/cmd/authz/service.go +++ b/cmd/authz/service.go @@ -37,7 +37,7 @@ func ServiceCmd(imsConfig *ims.Config) *cobra.Command { cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS client ID.") cmd.Flags().StringVarP(&imsConfig.ClientSecret, "clientSecret", "p", "", "IMS client secret.") - cmd.Flags().StringVarP(&imsConfig.AuthorizationCode, "authorizationCode", "a", "", "Permanent authorization code.") + cmd.Flags().StringVarP(&imsConfig.AuthorizationCode, "authorizationCode", "x", "", "Permanent authorization code.") return cmd } diff --git a/cmd/dcr.go b/cmd/dcr.go index c794984..7ca9116 100644 --- a/cmd/dcr.go +++ b/cmd/dcr.go @@ -39,7 +39,6 @@ func registerCmd(imsConfig *ims.Config) *cobra.Command { }, } - cmd.Flags().StringVarP(&imsConfig.RegisterURL, "url", "u", "", "Registration endpoint URL.") cmd.Flags().StringVarP(&imsConfig.ClientName, "clientName", "n", "", "Client application name.") cmd.Flags().StringSliceVarP(&imsConfig.RedirectURIs, "redirectURIs", "r", []string{}, "Redirect URIs (comma-separated or multiple flags).") diff --git a/cmd/invalidate/access_token.go b/cmd/invalidate/access_token.go deleted file mode 100644 index ffe42f8..0000000 --- a/cmd/invalidate/access_token.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package invalidate - -import ( - "fmt" - - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func AccessTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "accessToken", - Aliases: []string{"acc"}, - Short: "Invalidate an access token.", - Long: "Invalidate an access token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - err := imsConfig.InvalidateToken() - if err != nil { - return fmt.Errorf("error invalidating the access token: %w", err) - } - fmt.Println("Token invalidated successfully.") - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.AccessToken, "accessToken", "t", "", "Access token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - - return cmd -} diff --git a/cmd/invalidate/device_token.go b/cmd/invalidate/device_token.go deleted file mode 100644 index 5ec8391..0000000 --- a/cmd/invalidate/device_token.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package invalidate - -import ( - "fmt" - - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func DeviceTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "deviceToken", - Aliases: []string{"dev"}, - Short: "invalidate a device token.", - Long: "invalidate a device token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - err := imsConfig.InvalidateToken() - if err != nil { - return fmt.Errorf("error invalidating the device token: %w", err) - } - fmt.Println("Token invalidated successfully.") - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.DeviceToken, "deviceToken", "t", "", "Device token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - cmd.Flags().BoolVarP(&imsConfig.Cascading, "cascading", "a", false, - "Also invalidate all tokens obtained with the device token.") - - return cmd -} diff --git a/cmd/invalidate/invalidate.go b/cmd/invalidate/invalidate.go new file mode 100644 index 0000000..4a5f82a --- /dev/null +++ b/cmd/invalidate/invalidate.go @@ -0,0 +1,98 @@ +// Copyright 2021 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. + +package invalidate + +import ( + "fmt" + + "github.com/adobe/imscli/ims" + "github.com/spf13/cobra" +) + +type tokenDef struct { + use string + alias string + label string + flagName string + field *string + successMsg string + extraFlags func(cmd *cobra.Command, imsConfig *ims.Config) +} + +func tokenCmd(imsConfig *ims.Config, def tokenDef) *cobra.Command { + cmd := &cobra.Command{ + Use: def.use, + Aliases: []string{def.alias}, + Short: fmt.Sprintf("Invalidate %s.", def.label), + Long: fmt.Sprintf("Invalidate %s.", def.label), + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + err := imsConfig.InvalidateToken() + if err != nil { + return fmt.Errorf("error invalidating the %s: %w", def.label, err) + } + fmt.Println(def.successMsg) + return nil + }, + } + + cmd.Flags().StringVarP(def.field, def.flagName, "t", "", def.label+".") + cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") + if def.extraFlags != nil { + def.extraFlags(cmd, imsConfig) + } + + return cmd +} + +func AccessTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "accessToken", alias: "acc", label: "access token", + flagName: "accessToken", field: &imsConfig.AccessToken, + successMsg: "Token invalidated successfully.", + }) +} + +func RefreshTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "refreshToken", alias: "ref", label: "refresh token", + flagName: "refreshToken", field: &imsConfig.RefreshToken, + successMsg: "Refresh token successfully invalidated.", + extraFlags: func(cmd *cobra.Command, c *ims.Config) { + cmd.Flags().BoolVarP(&c.Cascading, "cascading", "C", false, + "Also invalidate all tokens obtained with the refresh token.") + }, + }) +} + +func DeviceTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "deviceToken", alias: "dev", label: "device token", + flagName: "deviceToken", field: &imsConfig.DeviceToken, + successMsg: "Token invalidated successfully.", + extraFlags: func(cmd *cobra.Command, c *ims.Config) { + cmd.Flags().BoolVarP(&c.Cascading, "cascading", "C", false, + "Also invalidate all tokens obtained with the device token.") + }, + }) +} + +func ServiceTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "serviceToken", alias: "svc", label: "service token", + flagName: "serviceToken", field: &imsConfig.ServiceToken, + successMsg: "Service token successfully invalidated.", + extraFlags: func(cmd *cobra.Command, c *ims.Config) { + cmd.Flags().StringVarP(&c.ClientSecret, "clientSecret", "p", "", "IMS Client Secret.") + }, + }) +} diff --git a/cmd/invalidate/refresh_token.go b/cmd/invalidate/refresh_token.go deleted file mode 100644 index 6bd0334..0000000 --- a/cmd/invalidate/refresh_token.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package invalidate - -import ( - "fmt" - - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func RefreshTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "refreshToken", - Aliases: []string{"ref"}, - Short: "Invalidate a refresh token.", - Long: "Invalidate a refresh token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - err := imsConfig.InvalidateToken() - if err != nil { - return fmt.Errorf("error invalidating the refresh token: %w", err) - } - fmt.Println("Refresh token successfully invalidated.") - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.RefreshToken, "refreshToken", "t", "", "Refresh token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - cmd.Flags().BoolVarP(&imsConfig.Cascading, "cascading", "a", false, - "Also invalidate all tokens obtained with the refresh token.") - - return cmd -} diff --git a/cmd/invalidate/service_token.go b/cmd/invalidate/service_token.go deleted file mode 100644 index 91c5264..0000000 --- a/cmd/invalidate/service_token.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package invalidate - -import ( - "fmt" - - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func ServiceTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "serviceToken", - Aliases: []string{"svc"}, - Short: "Invalidate a service token.", - Long: "Invalidate a service token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - err := imsConfig.InvalidateToken() - if err != nil { - return fmt.Errorf("error invalidating the service token: %w", err) - } - fmt.Println("Service token successfully invalidated.") - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.ServiceToken, "serviceToken", "t", "", "Service token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - cmd.Flags().StringVarP(&imsConfig.ClientSecret, "clientSecret", "s", "", "IMS Client Secret.") - - return cmd -} diff --git a/cmd/refresh.go b/cmd/refresh.go index 648b59b..877dfb7 100644 --- a/cmd/refresh.go +++ b/cmd/refresh.go @@ -33,10 +33,10 @@ func refreshCmd(imsConfig *ims.Config) *cobra.Command { return fmt.Errorf("error during the token refresh: %w", err) } if imsConfig.FullOutput { - data := map[string]interface{}{ - "access_token": resp.AccessToken, - "refresh_token": resp.RefreshToken, - } + data := struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + }{resp.AccessToken, resp.RefreshToken} jsonData, err := json.MarshalIndent(data, "", " ") if err != nil { return fmt.Errorf("error marshalling full JSON response: %w", err) diff --git a/cmd/validate/access_token.go b/cmd/validate/access_token.go deleted file mode 100644 index b288d43..0000000 --- a/cmd/validate/access_token.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package validate - -import ( - "fmt" - - "github.com/adobe/imscli/cmd/pretty" - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func AccessTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "accessToken", - Aliases: []string{"acc"}, - Short: "Validate an access token.", - Long: "Validate an access token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - resp, err := imsConfig.ValidateToken() - if err != nil { - return fmt.Errorf("error validating the access token: %w", err) - } - if !resp.Valid { - return fmt.Errorf("invalid token: %v", resp.Info) - } - fmt.Println(pretty.JSON(resp.Info)) - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.AccessToken, "accessToken", "t", "", "Access token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - - return cmd -} diff --git a/cmd/validate/authorization_code.go b/cmd/validate/authorization_code.go deleted file mode 100644 index c910360..0000000 --- a/cmd/validate/authorization_code.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package validate - -import ( - "fmt" - - "github.com/adobe/imscli/cmd/pretty" - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func AuthzCodeCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "authorizationCode", - Aliases: []string{"authz"}, - Short: "Validate an authorization code.", - Long: "Validate an authorization code.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - resp, err := imsConfig.ValidateToken() - if err != nil { - return fmt.Errorf("error validating the authorization code: %w", err) - } - if !resp.Valid { - return fmt.Errorf("invalid token: %v", resp.Info) - } - fmt.Println(pretty.JSON(resp.Info)) - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.AuthorizationCode, "authorizationCode", "t", "", "Authorization code.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - - return cmd -} diff --git a/cmd/validate/device_token.go b/cmd/validate/device_token.go deleted file mode 100644 index 53807cf..0000000 --- a/cmd/validate/device_token.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package validate - -import ( - "fmt" - - "github.com/adobe/imscli/cmd/pretty" - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func DeviceTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "deviceToken", - Aliases: []string{"dev"}, - Short: "Validate a device token.", - Long: "Validate a device token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - resp, err := imsConfig.ValidateToken() - if err != nil { - return fmt.Errorf("error validating the device token: %w", err) - } - if !resp.Valid { - return fmt.Errorf("invalid token: %v", resp.Info) - } - fmt.Println(pretty.JSON(resp.Info)) - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.DeviceToken, "deviceToken", "t", "", "Device token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - - return cmd -} diff --git a/cmd/validate/refresh_token.go b/cmd/validate/refresh_token.go deleted file mode 100644 index 579bfe4..0000000 --- a/cmd/validate/refresh_token.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package validate - -import ( - "fmt" - - "github.com/adobe/imscli/cmd/pretty" - "github.com/adobe/imscli/ims" - "github.com/spf13/cobra" -) - -func RefreshTokenCmd(imsConfig *ims.Config) *cobra.Command { - cmd := &cobra.Command{ - Use: "refreshToken", - Aliases: []string{"ref"}, - Short: "Validate a refresh token.", - Long: "Validate a refresh token.", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - - - resp, err := imsConfig.ValidateToken() - if err != nil { - return fmt.Errorf("error validating the refresh token: %w", err) - } - if !resp.Valid { - return fmt.Errorf("invalid token: %v", resp.Info) - } - fmt.Println(pretty.JSON(resp.Info)) - return nil - }, - } - - cmd.Flags().StringVarP(&imsConfig.RefreshToken, "refreshToken", "t", "", "Refresh token.") - cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") - - return cmd -} diff --git a/cmd/validate/validate.go b/cmd/validate/validate.go new file mode 100644 index 0000000..10e7a18 --- /dev/null +++ b/cmd/validate/validate.go @@ -0,0 +1,82 @@ +// Copyright 2021 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. + +package validate + +import ( + "fmt" + + "github.com/adobe/imscli/cmd/pretty" + "github.com/adobe/imscli/ims" + "github.com/spf13/cobra" +) + +type tokenDef struct { + use string + alias string + label string + flagName string + field *string +} + +func tokenCmd(imsConfig *ims.Config, def tokenDef) *cobra.Command { + cmd := &cobra.Command{ + Use: def.use, + Aliases: []string{def.alias}, + Short: fmt.Sprintf("Validate %s.", def.label), + Long: fmt.Sprintf("Validate %s.", def.label), + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + resp, err := imsConfig.ValidateToken() + if err != nil { + return fmt.Errorf("error validating the %s: %w", def.label, err) + } + if !resp.Valid { + return fmt.Errorf("invalid token: %v", resp.Info) + } + fmt.Println(pretty.JSON(resp.Info)) + return nil + }, + } + + cmd.Flags().StringVarP(def.field, def.flagName, "t", "", def.label+".") + cmd.Flags().StringVarP(&imsConfig.ClientID, "clientID", "c", "", "IMS Client ID.") + + return cmd +} + +func AccessTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "accessToken", alias: "acc", label: "access token", + flagName: "accessToken", field: &imsConfig.AccessToken, + }) +} + +func RefreshTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "refreshToken", alias: "ref", label: "refresh token", + flagName: "refreshToken", field: &imsConfig.RefreshToken, + }) +} + +func DeviceTokenCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "deviceToken", alias: "dev", label: "device token", + flagName: "deviceToken", field: &imsConfig.DeviceToken, + }) +} + +func AuthzCodeCmd(imsConfig *ims.Config) *cobra.Command { + return tokenCmd(imsConfig, tokenDef{ + use: "authorizationCode", alias: "authz", label: "authorization code", + flagName: "authorizationCode", field: &imsConfig.AuthorizationCode, + }) +} diff --git a/ims/authz_user.go b/ims/authz_user.go index b02cba2..07685e2 100644 --- a/ims/authz_user.go +++ b/ims/authz_user.go @@ -24,8 +24,6 @@ import ( "github.com/pkg/browser" ) -const defaultPort = 8888 - // Validate that: // - the ims.Config struct has the necessary parameters for AuthorizeUser // - the provided environment exists @@ -42,6 +40,8 @@ func (i Config) validateAuthorizeUserConfig() error { return fmt.Errorf("missing client id parameter") case i.Organization == "": return fmt.Errorf("missing organization parameter") + case i.Port <= 0: + return fmt.Errorf("missing or invalid port parameter") case i.ClientSecret == "": if i.PublicClient { log.Println("all needed parameters verified not empty") @@ -55,21 +55,23 @@ func (i Config) validateAuthorizeUserConfig() error { return nil } -// AuthorizeUser uses the standard Oauth2 authorization code grant flow. The Oauth2 configuration is -// taken from the Config struct. +// AuthorizeUser uses the standard OAuth2 authorization code grant flow. func (i Config) AuthorizeUser() (string, error) { + return i.authorizeUser(false) +} + +// AuthorizeUserPKCE uses the OAuth2 authorization code grant flow with PKCE. +func (i Config) AuthorizeUserPKCE() (string, error) { + return i.authorizeUser(true) +} + +func (i Config) authorizeUser(pkce bool) (string, error) { // Perform parameter validation err := i.validateAuthorizeUserConfig() if err != nil { return "", fmt.Errorf("invalid parameters for login user: %w", err) } - // Use default port if not specified - port := i.Port - if port == 0 { - port = defaultPort - } - c, err := i.newIMSClient() if err != nil { return "", fmt.Errorf("error creating the IMS client: %w", err) @@ -80,8 +82,8 @@ func (i Config) AuthorizeUser() (string, error) { ClientID: i.ClientID, ClientSecret: i.ClientSecret, Scope: i.Scopes, - UsePKCE: i.PKCE, - RedirectURI: fmt.Sprintf("http://localhost:%d", port), + UsePKCE: pkce, + RedirectURI: fmt.Sprintf("http://localhost:%d", i.Port), OnError: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `

An error occurred

@@ -99,15 +101,15 @@ func (i Config) AuthorizeUser() (string, error) { return "", fmt.Errorf("create authorization server: %w", err) } - listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", i.Port)) if err != nil { - return "", fmt.Errorf("unable to listen at port %d", port) + return "", fmt.Errorf("unable to listen at port %d", i.Port) } defer listener.Close() log.Println("Local server successfully launched and contacted.") - localUrl := fmt.Sprintf("http://localhost:%d/", port) + localUrl := fmt.Sprintf("http://localhost:%d/", i.Port) // Suppress "Opening in existing browser session." messages from chromium-based // browsers. The CLI token output goes to stdout, so stray browser messages diff --git a/ims/config.go b/ims/config.go index 103e4e2..8500eca 100644 --- a/ims/config.go +++ b/ims/config.go @@ -41,12 +41,10 @@ type Config struct { Cascading bool Token string Port int - PKCE bool FullOutput bool Guid string AuthSrc string DecodeFulfillableData bool - RegisterURL string ClientName string RedirectURIs []string } @@ -54,7 +52,6 @@ type Config struct { // Access token information type TokenInfo struct { AccessToken string - Expires int //(response.ExpiresIn * time.Millisecond), Valid bool Info string } diff --git a/ims/config_test.go b/ims/config_test.go index 905d6df..c628844 100644 --- a/ims/config_test.go +++ b/ims/config_test.go @@ -224,6 +224,7 @@ func TestValidateAuthorizeUserConfig(t *testing.T) { ClientSecret: "s", Organization: "org", Scopes: []string{"openid"}, + Port: 8888, } tests := []struct { name string @@ -239,6 +240,7 @@ func TestValidateAuthorizeUserConfig(t *testing.T) { {name: "missing clientID", config: withField(validConfig, func(c *Config) { c.ClientID = "" }), wantErr: "missing client id"}, {name: "missing organization", config: withField(validConfig, func(c *Config) { c.Organization = "" }), wantErr: "missing organization"}, {name: "missing secret non-public", config: withField(validConfig, func(c *Config) { c.ClientSecret = "" }), wantErr: "missing client secret"}, + {name: "missing port", config: withField(validConfig, func(c *Config) { c.Port = 0 }), wantErr: "missing or invalid port"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -295,10 +297,10 @@ func TestValidateRegisterConfig(t *testing.T) { config Config wantErr string }{ - {name: "valid", config: Config{RegisterURL: "https://example.com/register", ClientName: "app", RedirectURIs: []string{"https://example.com/cb"}}, wantErr: ""}, - {name: "missing URL", config: Config{ClientName: "app", RedirectURIs: []string{"https://example.com/cb"}}, wantErr: "missing registration endpoint URL"}, - {name: "missing client name", config: Config{RegisterURL: "https://example.com/register", RedirectURIs: []string{"https://example.com/cb"}}, wantErr: "missing client name"}, - {name: "missing redirect URIs", config: Config{RegisterURL: "https://example.com/register", ClientName: "app"}, wantErr: "missing redirect URIs"}, + {name: "valid", config: Config{URL: "https://ims.example.com", ClientName: "app", RedirectURIs: []string{"https://example.com/cb"}}, wantErr: ""}, + {name: "missing URL", config: Config{ClientName: "app", RedirectURIs: []string{"https://example.com/cb"}}, wantErr: "missing IMS base URL"}, + {name: "missing client name", config: Config{URL: "https://ims.example.com", RedirectURIs: []string{"https://example.com/cb"}}, wantErr: "missing client name"}, + {name: "missing redirect URIs", config: Config{URL: "https://ims.example.com", ClientName: "app"}, wantErr: "missing redirect URIs"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/ims/exchange.go b/ims/exchange.go index 6bd1d43..c1405d4 100644 --- a/ims/exchange.go +++ b/ims/exchange.go @@ -12,7 +12,6 @@ package ims import ( "fmt" - "time" "github.com/adobe/ims-go/ims" ) @@ -62,6 +61,5 @@ func (i Config) ClusterExchange() (TokenInfo, error) { return TokenInfo{ AccessToken: r.AccessToken, - Expires: int(r.ExpiresIn * time.Millisecond), }, nil } diff --git a/ims/jwt_exchange.go b/ims/jwt_exchange.go index a30277b..42a91f4 100644 --- a/ims/jwt_exchange.go +++ b/ims/jwt_exchange.go @@ -13,6 +13,7 @@ package ims import ( "fmt" "os" + "strings" "time" "github.com/adobe/ims-go/ims" @@ -29,14 +30,20 @@ func (i Config) AuthorizeJWTExchange() (TokenInfo, error) { if err != nil { return TokenInfo{}, fmt.Errorf("error read private key file: %s, %w", i.PrivateKeyPath, err) } + defer func() { + for i := range key { + key[i] = 0 + } + }() // Metascopes are passed as generic claims with the format map[string]interface{} // where the strings are in the form: baseIMSUrl/s/metascope // and the interface{} is 'true' + baseURL := strings.TrimRight(i.URL, "/") claims := make(map[string]interface{}) for _, metascope := range i.Metascopes { - claims[fmt.Sprintf("%s/s/%s", i.URL, metascope)] = true + claims[fmt.Sprintf("%s/s/%s", baseURL, metascope)] = true } r, err := c.ExchangeJWT(&ims.ExchangeJWTRequest{ @@ -54,6 +61,5 @@ func (i Config) AuthorizeJWTExchange() (TokenInfo, error) { return TokenInfo{ AccessToken: r.AccessToken, - Expires: int(r.ExpiresIn * time.Millisecond), }, nil } diff --git a/ims/organizations.go b/ims/organizations.go index 5853c9b..c9fadc4 100644 --- a/ims/organizations.go +++ b/ims/organizations.go @@ -22,7 +22,7 @@ func (i Config) validateGetOrganizationsConfig() error { switch i.OrgsAPIVersion { case "v1", "v2", "v3", "v4", "v5", "v6": default: - return fmt.Errorf("invalid API version parameter, use something like v5") + return fmt.Errorf("invalid API version parameter, latest version is v6") } switch { diff --git a/ims/refresh.go b/ims/refresh.go index 185a258..82ce08e 100644 --- a/ims/refresh.go +++ b/ims/refresh.go @@ -12,8 +12,8 @@ package ims import ( "fmt" + "github.com/adobe/ims-go/ims" - "time" ) func (i Config) validateRefreshConfig() error { @@ -56,7 +56,6 @@ func (i Config) Refresh() (RefreshInfo, error) { return RefreshInfo{ TokenInfo: TokenInfo{ AccessToken: r.AccessToken, - Expires: int(r.ExpiresIn * time.Second), }, RefreshToken: r.RefreshToken, }, nil diff --git a/ims/register.go b/ims/register.go index 88e94d8..7f70e8c 100644 --- a/ims/register.go +++ b/ims/register.go @@ -15,8 +15,8 @@ type RegisterResponse struct { func (i Config) validateRegisterConfig() error { switch { - case i.RegisterURL == "": - return fmt.Errorf("missing registration endpoint URL parameter") + case i.URL == "": + return fmt.Errorf("missing IMS base URL parameter") case i.ClientName == "": return fmt.Errorf("missing client name parameter") case len(i.RedirectURIs) == 0: @@ -47,7 +47,9 @@ func (i Config) Register() (RegisterResponse, error) { "redirect_uris": %s }`, i.ClientName, redirectURIsJSON)) - req, err := http.NewRequest("POST", i.RegisterURL, payload) + endpoint := strings.TrimRight(i.URL, "/") + "/ims/register" + + req, err := http.NewRequest("POST", endpoint, payload) if err != nil { return RegisterResponse{}, fmt.Errorf("error creating request: %v", err) }