diff --git a/README.md b/README.md index 24a0aa0..8e6bdbb 100644 --- a/README.md +++ b/README.md @@ -569,6 +569,7 @@ You can either pass global arguments as CLI parameters, set an env variable or s | `--uri` | `UIPATH_URI` | `uri` | `https://cloud.uipath.com` | URL override | | `--organization` | `UIPATH_ORGANIZATION` | `string` | | Organization name | | `--tenant` | `UIPATH_TENANT` | `string` | | Tenant name | +| `--identity-uri` | `UIPATH_IDENTITY_URI` | `uri` | `https://cloud.uipath.com/identity_` | URL override for identity calls | | | `UIPATH_CLIENT_ID` | `string` | | Client Id | | | `UIPATH_CLIENT_SECRET` | `string` | | Client Secret | | | `UIPATH_PAT` | `string` | | Personal Access Token | diff --git a/auth/authenticator_context.go b/auth/authenticator_context.go index 25f4060..3c974f8 100644 --- a/auth/authenticator_context.go +++ b/auth/authenticator_context.go @@ -1,19 +1,23 @@ package auth +import "net/url" + // AuthenticatorContext provides information required for authenticating requests. type AuthenticatorContext struct { - Type string `json:"type"` - Config map[string]interface{} `json:"config"` - Debug bool `json:"debug"` - Insecure bool `json:"insecure"` - Request AuthenticatorRequest `json:"request"` + Type string `json:"type"` + Config map[string]interface{} `json:"config"` + IdentityUri url.URL `json:"identityUri"` + Debug bool `json:"debug"` + Insecure bool `json:"insecure"` + Request AuthenticatorRequest `json:"request"` } func NewAuthenticatorContext( authType string, config map[string]interface{}, + identityUri url.URL, debug bool, insecure bool, request AuthenticatorRequest) *AuthenticatorContext { - return &AuthenticatorContext{authType, config, debug, insecure, request} + return &AuthenticatorContext{authType, config, identityUri, debug, insecure, request} } diff --git a/auth/bearer_authenticator.go b/auth/bearer_authenticator.go index 4895840..ea82e84 100644 --- a/auth/bearer_authenticator.go +++ b/auth/bearer_authenticator.go @@ -2,7 +2,6 @@ package auth import ( "fmt" - "net/url" "os" "github.com/UiPath/uipathcli/cache" @@ -10,7 +9,6 @@ import ( const ClientIdEnvVarName = "UIPATH_CLIENT_ID" const ClientSecretEnvVarName = "UIPATH_CLIENT_SECRET" //nolint // This is not a secret but just the env variable name -const IdentityUriEnvVarName = "UIPATH_IDENTITY_URI" // The BearerAuthenticator calls the identity token-endpoint to retrieve a JWT bearer token. // It requires clientId and clientSecret. @@ -26,21 +24,9 @@ func (a BearerAuthenticator) Auth(ctx AuthenticatorContext) AuthenticatorResult if err != nil { return *AuthenticatorError(fmt.Errorf("Invalid bearer authenticator configuration: %w", err)) } - identityBaseUri := config.IdentityUri - if identityBaseUri == nil { - requestUrl, err := url.Parse(ctx.Request.URL) - if err != nil { - return *AuthenticatorError(fmt.Errorf("Invalid request url '%s': %w", ctx.Request.URL, err)) - } - identityBaseUri, err = url.Parse(fmt.Sprintf("%s://%s/identity_", requestUrl.Scheme, requestUrl.Host)) - if err != nil { - return *AuthenticatorError(fmt.Errorf("Invalid identity url '%s': %w", ctx.Request.URL, err)) - } - } - identityClient := newIdentityClient(a.cache) tokenRequest := newTokenRequest( - *identityBaseUri, + config.IdentityUri, config.GrantType, config.Scopes, config.ClientId, @@ -85,15 +71,7 @@ func (a BearerAuthenticator) getConfig(ctx AuthenticatorContext) (*bearerAuthent if err != nil { return nil, err } - var uri *url.URL - uriString, err := a.parseRequiredString(ctx.Config, "uri", os.Getenv(IdentityUriEnvVarName)) - if err == nil { - uri, err = url.Parse(uriString) - if err != nil { - return nil, fmt.Errorf("Error parsing identity uri: %w", err) - } - } - return newBearerAuthenticatorConfig(grantType, scopes, clientId, clientSecret, properties, uri), nil + return newBearerAuthenticatorConfig(grantType, scopes, clientId, clientSecret, properties, ctx.IdentityUri), nil } func (a BearerAuthenticator) parseProperties(config map[string]interface{}) (map[string]string, error) { diff --git a/auth/bearer_authenticator_config.go b/auth/bearer_authenticator_config.go index 5505af4..375b0a9 100644 --- a/auth/bearer_authenticator_config.go +++ b/auth/bearer_authenticator_config.go @@ -8,7 +8,7 @@ type bearerAuthenticatorConfig struct { ClientId string ClientSecret string Properties map[string]string - IdentityUri *url.URL + IdentityUri url.URL } func newBearerAuthenticatorConfig( @@ -17,6 +17,6 @@ func newBearerAuthenticatorConfig( clientId string, clientSecret string, properties map[string]string, - identityUri *url.URL) *bearerAuthenticatorConfig { + identityUri url.URL) *bearerAuthenticatorConfig { return &bearerAuthenticatorConfig{grantType, scopes, clientId, clientSecret, properties, identityUri} } diff --git a/auth/oauth_authenticator.go b/auth/oauth_authenticator.go index caf10f6..69ae271 100644 --- a/auth/oauth_authenticator.go +++ b/auth/oauth_authenticator.go @@ -35,18 +35,7 @@ func (a OAuthAuthenticator) Auth(ctx AuthenticatorContext) AuthenticatorResult { if err != nil { return *AuthenticatorError(fmt.Errorf("Invalid oauth authenticator configuration: %w", err)) } - identityBaseUri := config.IdentityUri - if identityBaseUri == nil { - requestUrl, err := url.Parse(ctx.Request.URL) - if err != nil { - return *AuthenticatorError(fmt.Errorf("Invalid request url '%s': %w", ctx.Request.URL, err)) - } - identityBaseUri, err = url.Parse(fmt.Sprintf("%s://%s/identity_", requestUrl.Scheme, requestUrl.Host)) - if err != nil { - return *AuthenticatorError(fmt.Errorf("Invalid identity url '%s': %w", ctx.Request.URL, err)) - } - } - token, err := a.retrieveToken(*identityBaseUri, *config, ctx.Insecure) + token, err := a.retrieveToken(config.IdentityUri, *config, ctx.Insecure) if err != nil { return *AuthenticatorError(fmt.Errorf("Error retrieving access token: %w", err)) } @@ -176,15 +165,7 @@ func (a OAuthAuthenticator) getConfig(ctx AuthenticatorContext) (*oauthAuthentic if err != nil { return nil, err } - var uri *url.URL - uriString, err := a.parseRequiredString(ctx.Config, "uri") - if err == nil { - uri, err = url.Parse(uriString) - if err != nil { - return nil, fmt.Errorf("Error parsing identity uri: %w", err) - } - } - return newOAuthAuthenticatorConfig(clientId, *parsedRedirectUri, scopes, uri), nil + return newOAuthAuthenticatorConfig(clientId, *parsedRedirectUri, scopes, ctx.IdentityUri), nil } func (a OAuthAuthenticator) parseRequiredString(config map[string]interface{}, name string) (string, error) { diff --git a/auth/oauth_authenticator_config.go b/auth/oauth_authenticator_config.go index 6bf9ea3..8847859 100644 --- a/auth/oauth_authenticator_config.go +++ b/auth/oauth_authenticator_config.go @@ -6,13 +6,13 @@ type oauthAuthenticatorConfig struct { ClientId string RedirectUrl url.URL Scopes string - IdentityUri *url.URL + IdentityUri url.URL } func newOAuthAuthenticatorConfig( clientId string, redirectUrl url.URL, scopes string, - identityUri *url.URL) *oauthAuthenticatorConfig { + identityUri url.URL) *oauthAuthenticatorConfig { return &oauthAuthenticatorConfig{clientId, redirectUrl, scopes, identityUri} } diff --git a/auth/oauth_authenticator_test.go b/auth/oauth_authenticator_test.go index 4c24009..7a9563f 100644 --- a/auth/oauth_authenticator_test.go +++ b/auth/oauth_authenticator_test.go @@ -23,7 +23,7 @@ func TestOAuthAuthenticatorNotEnabled(t *testing.T) { "scopes": "OR.Users", } request := NewAuthenticatorRequest("http:/localhost", map[string]string{}) - context := NewAuthenticatorContext("login", config, false, false, *request) + context := NewAuthenticatorContext("login", config, createIdentityUrl(""), false, false, *request) authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil) result := authenticator.Auth(*context) @@ -44,7 +44,7 @@ func TestOAuthAuthenticatorPreservesExistingHeaders(t *testing.T) { "my-header": "my-value", } request := NewAuthenticatorRequest("http:/localhost", headers) - context := NewAuthenticatorContext("login", config, false, false, *request) + context := NewAuthenticatorContext("login", config, createIdentityUrl(""), false, false, *request) authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil) result := authenticator.Auth(*context) @@ -56,38 +56,6 @@ func TestOAuthAuthenticatorPreservesExistingHeaders(t *testing.T) { } } -func TestOAuthAuthenticatorInvalidRequestUrl(t *testing.T) { - config := map[string]interface{}{ - "clientId": "my-client-id", - "redirectUri": "http://localhost:0", - "scopes": "OR.Users", - } - request := NewAuthenticatorRequest("://invalid", map[string]string{}) - context := NewAuthenticatorContext("login", config, false, false, *request) - - authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil) - result := authenticator.Auth(*context) - if result.Error != `Invalid request url '://invalid': parse "://invalid": missing protocol scheme` { - t.Errorf("Expected error with invalid request url, but got: %v", result.Error) - } -} - -func TestOAuthAuthenticatorInvalidIdentityUrl(t *testing.T) { - config := map[string]interface{}{ - "clientId": "my-client-id", - "redirectUri": "http://localhost:0", - "scopes": "OR.Users", - } - request := NewAuthenticatorRequest("INVALID-URL", map[string]string{}) - context := NewAuthenticatorContext("login", config, false, false, *request) - - authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil) - result := authenticator.Auth(*context) - if result.Error != `Invalid identity url 'INVALID-URL': parse ":///identity_": missing protocol scheme` { - t.Errorf("Expected error with invalid request url, but got: %v", result.Error) - } -} - func TestOAuthAuthenticatorInvalidConfig(t *testing.T) { config := map[string]interface{}{ "clientId": 1, @@ -95,7 +63,7 @@ func TestOAuthAuthenticatorInvalidConfig(t *testing.T) { "scopes": "OR.Users", } request := NewAuthenticatorRequest("http:/localhost", map[string]string{}) - context := NewAuthenticatorContext("login", config, false, false, *request) + context := NewAuthenticatorContext("login", config, createIdentityUrl(""), false, false, *request) authenticator := NewOAuthAuthenticator(cache.NewFileCache(), nil) result := authenticator.Auth(*context) @@ -109,9 +77,9 @@ func TestOAuthFlowIdentityFails(t *testing.T) { ResponseStatus: 400, ResponseBody: "Invalid token request", } - identityUrl := identityServerFake.Start(t) + identityBaseUrl := identityServerFake.Start(t) - context := createAuthContext(identityUrl) + context := createAuthContext(identityBaseUrl) loginUrl, resultChannel := callAuthenticator(context) performLogin(loginUrl, t) @@ -126,9 +94,9 @@ func TestOAuthFlowSuccessful(t *testing.T) { ResponseStatus: 200, ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`, } - identityUrl := identityServerFake.Start(t) + identityBaseUrl := identityServerFake.Start(t) - context := createAuthContext(identityUrl) + context := createAuthContext(identityBaseUrl) loginUrl, resultChannel := callAuthenticator(context) performLogin(loginUrl, t) @@ -142,42 +110,14 @@ func TestOAuthFlowSuccessful(t *testing.T) { } } -func TestOAuthFlowWithCustomIdentityUri(t *testing.T) { - identityServerFake := identityServerFake{ - ResponseStatus: 200, - ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`, - } - identityUrl := identityServerFake.Start(t) - config := map[string]interface{}{ - "clientId": newClientId(), - "redirectUri": "http://localhost:0", - "scopes": "OR.Users", - "uri": identityUrl.String() + "/identity_", - } - request := NewAuthenticatorRequest("no-url", map[string]string{}) - context := NewAuthenticatorContext("login", config, false, false, *request) - - loginUrl, resultChannel := callAuthenticator(*context) - performLogin(loginUrl, t) - - result := <-resultChannel - if result.Error != "" { - t.Errorf("Expected no error when performing oauth flow, but got: %v", result.Error) - } - authorizationHeader := result.RequestHeader["Authorization"] - if authorizationHeader != "Bearer my-access-token" { - t.Errorf("Expected JWT bearer token in authorization header, but got: %v", authorizationHeader) - } -} - func TestOAuthFlowIsCached(t *testing.T) { identityServerFake := identityServerFake{ ResponseStatus: 200, ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`, } - identityUrl := identityServerFake.Start(t) + identityBaseUrl := identityServerFake.Start(t) - context := createAuthContext(identityUrl) + context := createAuthContext(identityBaseUrl) loginUrl, resultChannel := callAuthenticator(context) performLogin(loginUrl, t) <-resultChannel @@ -217,9 +157,9 @@ func TestShowsSuccessfullyLoggedInPage(t *testing.T) { ResponseStatus: 200, ResponseBody: `{"access_token": "my-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Users"}`, } - identityUrl := identityServerFake.Start(t) + identityBaseUrl := identityServerFake.Start(t) - context := createAuthContext(identityUrl) + context := createAuthContext(identityBaseUrl) loginUrl, _ := callAuthenticator(context) responseBody := performLogin(loginUrl, t) @@ -283,17 +223,26 @@ func callAuthenticator(context AuthenticatorContext) (url.URL, chan Authenticato return *url, resultChannel } -func createAuthContext(identityUrl url.URL) AuthenticatorContext { +func createAuthContext(baseUrl url.URL) AuthenticatorContext { config := map[string]interface{}{ "clientId": newClientId(), "redirectUri": "http://localhost:0", "scopes": "OR.Users", } - request := NewAuthenticatorRequest(fmt.Sprintf("%s://%s", identityUrl.Scheme, identityUrl.Host), map[string]string{}) - context := NewAuthenticatorContext("login", config, false, false, *request) + identityUrl := createIdentityUrl(baseUrl.Host) + request := NewAuthenticatorRequest(fmt.Sprintf("%s://%s", baseUrl.Scheme, baseUrl.Host), map[string]string{}) + context := NewAuthenticatorContext("login", config, identityUrl, false, false, *request) return *context } +func createIdentityUrl(hostName string) url.URL { + if hostName == "" { + hostName = "localhost" + } + identityUrl, _ := url.Parse(fmt.Sprintf("http://%s/identity_", hostName)) + return *identityUrl +} + func performLogin(loginUrl url.URL, t *testing.T) string { redirectUri := loginUrl.Query().Get("redirect_uri") state := loginUrl.Query().Get("state") diff --git a/commandline/command_builder.go b/commandline/command_builder.go index b6c22a2..1fc6b3c 100644 --- a/commandline/command_builder.go +++ b/commandline/command_builder.go @@ -26,6 +26,7 @@ const insecureFlagName = "insecure" const debugFlagName = "debug" const profileFlagName = "profile" const uriFlagName = "uri" +const identityUriFlagName = "identity-uri" const organizationFlagName = "organization" const tenantFlagName = "tenant" const helpFlagName = "help" @@ -41,6 +42,7 @@ var predefinedFlags = []string{ debugFlagName, profileFlagName, uriFlagName, + identityUriFlagName, organizationFlagName, tenantFlagName, helpFlagName, @@ -220,6 +222,32 @@ func (b CommandBuilder) createBaseUri(operation parser.Operation, config config. return builder.Uri(), nil } +func (b CommandBuilder) createIdentityUri(context *cli.Context, config config.Config, baseUri url.URL) (*url.URL, error) { + uri := context.String(identityUriFlagName) + if uri != "" { + identityUri, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("Error parsing %s argument: %w", identityUriFlagName, err) + } + return identityUri, nil + } + + value := config.Auth.Config["uri"] + uri, valid := value.(string) + if valid && uri != "" { + identityUri, err := url.Parse(uri) + if err != nil { + return nil, fmt.Errorf("Error parsing identity uri config: %w", err) + } + return identityUri, nil + } + identityUri, err := url.Parse(fmt.Sprintf("%s://%s/identity_", baseUri.Scheme, baseUri.Host)) + if err != nil { + return nil, fmt.Errorf("Error parsing identity uri: %w", err) + } + return identityUri, nil +} + func (b CommandBuilder) parseUriArgument(context *cli.Context) (*url.URL, error) { uriFlag := context.String(uriFlagName) if uriFlag == "" { @@ -363,6 +391,11 @@ func (b CommandBuilder) createOperationCommand(operation parser.Operation) *cli. } insecure := context.Bool(insecureFlagName) || config.Insecure debug := context.Bool(debugFlagName) || config.Debug + identityUri, err := b.createIdentityUri(context, *config, baseUri) + if err != nil { + return err + } + executionContext := executor.NewExecutionContext( organization, tenant, @@ -375,6 +408,7 @@ func (b CommandBuilder) createOperationCommand(operation parser.Operation) *cli. config.Auth, insecure, debug, + *identityUri, operation.Plugin) if wait != "" { @@ -875,6 +909,12 @@ func (b CommandBuilder) CreateDefaultFlags(hidden bool) []cli.Flag { Value: "", Hidden: hidden, }, + &cli.StringFlag{ + Name: identityUriFlagName, + Usage: "Identity Server URI", + EnvVars: []string{"UIPATH_IDENTITY_URI"}, + Hidden: hidden, + }, b.VersionFlag(hidden), } } diff --git a/commandline/parameter_formatter.go b/commandline/parameter_formatter.go index 77fe110..e238aa4 100644 --- a/commandline/parameter_formatter.go +++ b/commandline/parameter_formatter.go @@ -2,7 +2,7 @@ package commandline import ( "fmt" - "slices" + "sort" "strings" "github.com/UiPath/uipathcli/parser" @@ -68,7 +68,7 @@ func (f parameterFormatter) descriptionFields(parameter parser.Parameter) []inte func (f parameterFormatter) usageExample(parameter parser.Parameter) string { parameters := f.collectUsageParameters(parameter, "") - slices.Sort(parameters) + sort.Strings(parameters) builder := strings.Builder{} for _, value := range parameters { diff --git a/executor/execution_context.go b/executor/execution_context.go index 9768c36..d800473 100644 --- a/executor/execution_context.go +++ b/executor/execution_context.go @@ -22,6 +22,7 @@ type ExecutionContext struct { AuthConfig config.AuthConfig Insecure bool Debug bool + IdentityUri url.URL Plugin plugin.CommandPlugin } @@ -37,6 +38,7 @@ func NewExecutionContext( authConfig config.AuthConfig, insecure bool, debug bool, + identityUri url.URL, plugin plugin.CommandPlugin) *ExecutionContext { - return &ExecutionContext{organization, tenant, method, uri, route, contentType, input, parameters, authConfig, insecure, debug, plugin} + return &ExecutionContext{organization, tenant, method, uri, route, contentType, input, parameters, authConfig, insecure, debug, identityUri, plugin} } diff --git a/executor/http_executor.go b/executor/http_executor.go index d5233dc..e032b3d 100644 --- a/executor/http_executor.go +++ b/executor/http_executor.go @@ -144,9 +144,9 @@ func (e HttpExecutor) formatUri(baseUri url.URL, route string, pathParameters [] return e.validateUri(formatter.Uri()) } -func (e HttpExecutor) executeAuthenticators(authConfig config.AuthConfig, debug bool, insecure bool, request *http.Request) (*auth.AuthenticatorResult, error) { +func (e HttpExecutor) executeAuthenticators(authConfig config.AuthConfig, identityUri url.URL, debug bool, insecure bool, request *http.Request) (*auth.AuthenticatorResult, error) { authRequest := *auth.NewAuthenticatorRequest(request.URL.String(), map[string]string{}) - ctx := *auth.NewAuthenticatorContext(authConfig.Type, authConfig.Config, debug, insecure, authRequest) + ctx := *auth.NewAuthenticatorContext(authConfig.Type, authConfig.Config, identityUri, debug, insecure, authRequest) for _, authProvider := range e.authenticators { result := authProvider.Auth(ctx) if result.Error != "" { @@ -318,7 +318,7 @@ func (e HttpExecutor) call(context ExecutionContext, writer output.OutputWriter, request.Header.Add("Content-Type", contentType) } e.addHeaders(request, context.Parameters.Header()) - auth, err := e.executeAuthenticators(context.AuthConfig, context.Debug, context.Insecure, request) + auth, err := e.executeAuthenticators(context.AuthConfig, context.IdentityUri, context.Debug, context.Insecure, request) if err != nil { return err } diff --git a/executor/plugin_executor.go b/executor/plugin_executor.go index c6b1d80..0d0d21b 100644 --- a/executor/plugin_executor.go +++ b/executor/plugin_executor.go @@ -19,9 +19,9 @@ type PluginExecutor struct { authenticators []auth.Authenticator } -func (e PluginExecutor) executeAuthenticators(baseUri url.URL, authConfig config.AuthConfig, debug bool, insecure bool) (*auth.AuthenticatorResult, error) { +func (e PluginExecutor) executeAuthenticators(baseUri url.URL, authConfig config.AuthConfig, identityUri url.URL, debug bool, insecure bool) (*auth.AuthenticatorResult, error) { authRequest := *auth.NewAuthenticatorRequest(baseUri.String(), map[string]string{}) - ctx := *auth.NewAuthenticatorContext(authConfig.Type, authConfig.Config, debug, insecure, authRequest) + ctx := *auth.NewAuthenticatorContext(authConfig.Type, authConfig.Config, identityUri, debug, insecure, authRequest) for _, authProvider := range e.authenticators { result := authProvider.Auth(ctx) if result.Error != "" { @@ -51,7 +51,7 @@ func (e PluginExecutor) pluginAuth(auth *auth.AuthenticatorResult) plugin.AuthRe } func (e PluginExecutor) Call(context ExecutionContext, writer output.OutputWriter, logger log.Logger) error { - auth, err := e.executeAuthenticators(context.BaseUri, context.AuthConfig, context.Debug, context.Insecure) + auth, err := e.executeAuthenticators(context.BaseUri, context.AuthConfig, context.IdentityUri, context.Debug, context.Insecure) if err != nil { return err } diff --git a/test/auth_test.go b/test/auth_test.go index 0e29252..9dea567 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -122,6 +122,35 @@ paths: } } +func TestBearerAuthWithInvalidIdentityUriParameter(t *testing.T) { + config := ` +profiles: + - name: default + auth: + clientId: success-client-id + clientSecret: success-client-secret +` + definition := ` +paths: + /ping: + get: + operationId: ping +` + + context := NewContextBuilder(). + WithDefinition("myservice", definition). + WithConfig(config). + WithResponse(200, ""). + WithIdentityResponse(200, `{"access_token": "my-jwt-access-token", "expires_in": 3600, "token_type": "Bearer", "scope": "OR.Ping"}`). + Build() + + result := RunCli([]string{"myservice", "ping", "--identity-uri", ":invalid"}, context) + + if !strings.Contains(result.Error.Error(), "Error parsing identity-uri argument") { + t.Errorf("Expected identity uri parsing error, but got: %v", result.Error) + } +} + func TestBearerAuthTokenIsCached(t *testing.T) { config := ` profiles: diff --git a/test/show_command_test.go b/test/show_command_test.go index e8fba2d..9171d5d 100644 --- a/test/show_command_test.go +++ b/test/show_command_test.go @@ -141,7 +141,7 @@ paths: names = append(names, parameter["name"].(string)) } - expectedNames := []string{"debug", "profile", "uri", "organization", "tenant", "insecure", "output", "query", "wait", "wait-timeout", "file", "version", "help"} + expectedNames := []string{"debug", "profile", "uri", "organization", "tenant", "insecure", "output", "query", "wait", "wait-timeout", "file", "identity-uri", "version", "help"} if !reflect.DeepEqual(names, expectedNames) { t.Errorf("Unexpected global parameters in output, expected: %v but got: %v", expectedNames, names) }