Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion cmd/admin/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
2 changes: 1 addition & 1 deletion cmd/admin/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
4 changes: 2 additions & 2 deletions cmd/authz/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand Down
3 changes: 1 addition & 2 deletions cmd/authz/pkce.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/authz/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions cmd/invalidate/invalidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func RefreshTokenCmd(imsConfig *ims.Config) *cobra.Command {
flagName: "refreshToken", field: &imsConfig.RefreshToken,
successMsg: "Refresh token successfully invalidated.",
extraFlags: func(cmd *cobra.Command, c *ims.Config) {
cmd.Flags().BoolVarP(&c.Cascading, "cascading", "a", false,
cmd.Flags().BoolVarP(&c.Cascading, "cascading", "C", false,
"Also invalidate all tokens obtained with the refresh token.")
},
})
Expand All @@ -80,7 +80,7 @@ func DeviceTokenCmd(imsConfig *ims.Config) *cobra.Command {
flagName: "deviceToken", field: &imsConfig.DeviceToken,
successMsg: "Token invalidated successfully.",
extraFlags: func(cmd *cobra.Command, c *ims.Config) {
cmd.Flags().BoolVarP(&c.Cascading, "cascading", "a", false,
cmd.Flags().BoolVarP(&c.Cascading, "cascading", "C", false,
"Also invalidate all tokens obtained with the device token.")
},
})
Expand All @@ -92,7 +92,7 @@ func ServiceTokenCmd(imsConfig *ims.Config) *cobra.Command {
flagName: "serviceToken", field: &imsConfig.ServiceToken,
successMsg: "Service token successfully invalidated.",
extraFlags: func(cmd *cobra.Command, c *ims.Config) {
cmd.Flags().StringVarP(&c.ClientSecret, "clientSecret", "s", "", "IMS Client Secret.")
cmd.Flags().StringVarP(&c.ClientSecret, "clientSecret", "p", "", "IMS Client Secret.")
},
})
}
8 changes: 4 additions & 4 deletions cmd/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 17 additions & 15 deletions ims/authz_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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, `
<h1>An error occurred</h1>
Expand All @@ -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
Expand Down
2 changes: 0 additions & 2 deletions ims/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ type Config struct {
Cascading bool
Token string
Port int
PKCE bool
FullOutput bool
Guid string
AuthSrc string
Expand All @@ -53,7 +52,6 @@ type Config struct {
// Access token information
type TokenInfo struct {
AccessToken string
Expires int //(response.ExpiresIn * time.Millisecond),
Valid bool
Info string
}
Expand Down
2 changes: 2 additions & 0 deletions ims/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ func TestValidateAuthorizeUserConfig(t *testing.T) {
ClientSecret: "s",
Organization: "org",
Scopes: []string{"openid"},
Port: 8888,
}
tests := []struct {
name string
Expand All @@ -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) {
Expand Down
2 changes: 0 additions & 2 deletions ims/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ package ims

import (
"fmt"
"time"

"github.com/adobe/ims-go/ims"
)
Expand Down Expand Up @@ -62,6 +61,5 @@ func (i Config) ClusterExchange() (TokenInfo, error) {

return TokenInfo{
AccessToken: r.AccessToken,
Expires: int(r.ExpiresIn * time.Millisecond),
}, nil
}
10 changes: 8 additions & 2 deletions ims/jwt_exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ package ims
import (
"fmt"
"os"
"strings"
"time"

"github.com/adobe/ims-go/ims"
Expand All @@ -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{
Expand All @@ -54,6 +61,5 @@ func (i Config) AuthorizeJWTExchange() (TokenInfo, error) {

return TokenInfo{
AccessToken: r.AccessToken,
Expires: int(r.ExpiresIn * time.Millisecond),
}, nil
}
2 changes: 1 addition & 1 deletion ims/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 1 addition & 2 deletions ims/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ package ims

import (
"fmt"

"github.com/adobe/ims-go/ims"
"time"
)

func (i Config) validateRefreshConfig() error {
Expand Down Expand Up @@ -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
Expand Down