From c862ff2b04068522293b075c3e39ca5c1ca6bc02 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Wed, 2 Aug 2023 14:17:25 -0700 Subject: [PATCH] Enhance auth-login - open browser on wsl (#2576) --- cli/azd/cmd/auth_login.go | 10 +++++- cli/azd/cmd/util.go | 52 ++++++++++++++++++++++++++------ cli/azd/pkg/auth/manager.go | 36 +++++++++++++++++----- cli/azd/pkg/auth/manager_test.go | 2 +- go.mod | 5 +-- go.sum | 8 +++-- 6 files changed, 89 insertions(+), 24 deletions(-) diff --git a/cli/azd/cmd/auth_login.go b/cli/azd/cmd/auth_login.go index 24e81b90745..47e86fd81dd 100644 --- a/cli/azd/cmd/auth_login.go +++ b/cli/azd/cmd/auth_login.go @@ -476,7 +476,15 @@ func (la *loginAction) login(ctx context.Context) error { return fmt.Errorf("logging in: %w", err) } } else { - _, err := la.authManager.LoginInteractive(ctx, la.flags.redirectPort, la.flags.tenantID, la.flags.scopes) + _, err := la.authManager.LoginInteractive(ctx, la.flags.scopes, + &auth.LoginInteractiveOptions{ + TenantID: la.flags.tenantID, + RedirectPort: la.flags.redirectPort, + WithOpenUrl: func(url string) error { + openWithDefaultBrowser(ctx, la.console, url) + return nil + }, + }) if err != nil { return fmt.Errorf("logging in: %w", err) } diff --git a/cli/azd/cmd/util.go b/cli/azd/cmd/util.go index 00570ff4b1a..6647863bee9 100644 --- a/cli/azd/cmd/util.go +++ b/cli/azd/cmd/util.go @@ -7,8 +7,9 @@ import ( "context" "errors" "fmt" + "log" "os" - "os/exec" + "strings" "time" "github.com/AlecAivazis/survey/v2" @@ -17,6 +18,7 @@ import ( "github.com/azure/azure-dev/cli/azd/internal/tracing" "github.com/azure/azure-dev/cli/azd/pkg/environment" "github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext" + azdExec "github.com/azure/azure-dev/cli/azd/pkg/exec" "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/output" "github.com/azure/azure-dev/cli/azd/pkg/project" @@ -328,22 +330,24 @@ func openWithDefaultBrowser(ctx context.Context, console input.Console, url stri return } - console.Message(ctx, fmt.Sprintf("Opening %s in the default browser...\n", url)) + cmdRunner := azdExec.NewCommandRunner(nil) + // In Codespaces and devcontainers a $BROWSER environment variable is // present whose value is an executable that launches the browser when // called with the form: // $BROWSER - const BrowserEnvVarName = "BROWSER" if envBrowser := os.Getenv(BrowserEnvVarName); len(envBrowser) > 0 { - err := exec.Command(envBrowser, url).Run() + _, err := cmdRunner.Run(ctx, azdExec.RunArgs{ + Cmd: envBrowser, + Args: []string{url}, + }) if err == nil { return } - fmt.Fprintf( - console.Handles().Stderr, - "warning: failed to open browser configured by $BROWSER: %s\n. Trying with default browser.", + log.Printf( + "warning: failed to open browser configured by $BROWSER: %s\nTrying with default browser.\n", err.Error(), ) } @@ -353,11 +357,39 @@ func openWithDefaultBrowser(ctx context.Context, console input.Console, url stri return } - fmt.Fprintf( - console.Handles().Stderr, - "warning: failed to open default browser: %s\n", err.Error(), + log.Printf( + "warning: failed to open default browser: %s\nTrying manual launch.", err.Error(), ) + // wsl manual launch. Trying cmd first, and pwsh second + _, err = cmdRunner.Run(ctx, azdExec.RunArgs{ + Cmd: "cmd.exe", + // cmd notes: + // /c -> run command + // /s -> quoted string, use the text within the quotes as it is + // Replace `&` for `^&` -> cmd require to scape the & to avoid using it as a command concat + Args: []string{ + "/s", "/c", fmt.Sprintf("start %s", strings.ReplaceAll(url, "&", "^&")), + }, + }) + if err == nil { + return + } + log.Printf( + "warning: failed to open browser with cmd: %s\nTrying powershell.", err.Error(), + ) + + _, err = cmdRunner.Run(ctx, azdExec.RunArgs{ + Cmd: "powershell.exe", + Args: []string{ + "-NoProfile", "-Command", "Start-Process", fmt.Sprintf("\"%s\"", url), + }, + }) + if err == nil { + return + } + + log.Printf("warning: failed to use manual launch: %s\n", err.Error()) console.Message(ctx, fmt.Sprintf("Azd was unable to open the next url. Please try it manually: %s", url)) } diff --git a/cli/azd/pkg/auth/manager.go b/cli/azd/pkg/auth/manager.go index 21878821102..2035407b5ba 100644 --- a/cli/azd/pkg/auth/manager.go +++ b/cli/azd/pkg/auth/manager.go @@ -443,21 +443,43 @@ func (m *Manager) newCredentialFromCloudShell() (azcore.TokenCredential, error) return NewCloudShellCredential(m.httpClient), nil } +// WithOpenUrl defines a custom strategy for browsing to the url. +type WithOpenUrl func(url string) error + +// LoginInteractiveOptions holds the optional inputs for interactive login. +type LoginInteractiveOptions struct { + TenantID string + RedirectPort int + WithOpenUrl WithOpenUrl +} + +// LoginInteractive opens a browser for authenticate the user. func (m *Manager) LoginInteractive( - ctx context.Context, redirectPort int, tenantID string, scopes []string) (azcore.TokenCredential, error) { + ctx context.Context, + scopes []string, + options *LoginInteractiveOptions) (azcore.TokenCredential, error) { if scopes == nil { scopes = LoginScopes } - options := []public.AcquireInteractiveOption{} - if redirectPort > 0 { - options = append(options, public.WithRedirectURI(fmt.Sprintf("http://localhost:%d", redirectPort))) + acquireTokenOptions := []public.AcquireInteractiveOption{} + if options == nil { + options = &LoginInteractiveOptions{} } - if tenantID != "" { - options = append(options, public.WithTenantID(tenantID)) + if options.RedirectPort > 0 { + acquireTokenOptions = append( + acquireTokenOptions, public.WithRedirectURI(fmt.Sprintf("http://localhost:%d", options.RedirectPort))) + } + + if options.TenantID != "" { + acquireTokenOptions = append(acquireTokenOptions, public.WithTenantID(options.TenantID)) + } + + if options.WithOpenUrl != nil { + acquireTokenOptions = append(acquireTokenOptions, public.WithOpenURL(options.WithOpenUrl)) } - res, err := m.publicClient.AcquireTokenInteractive(ctx, scopes, options...) + res, err := m.publicClient.AcquireTokenInteractive(ctx, scopes, acquireTokenOptions...) if err != nil { return nil, err } diff --git a/cli/azd/pkg/auth/manager_test.go b/cli/azd/pkg/auth/manager_test.go index 90a61c7bbb1..001f1f9a076 100644 --- a/cli/azd/pkg/auth/manager_test.go +++ b/cli/azd/pkg/auth/manager_test.go @@ -206,7 +206,7 @@ func TestLoginInteractive(t *testing.T) { publicClient: &mockPublicClient{}, } - cred, err := m.LoginInteractive(context.Background(), 0, "", nil) + cred, err := m.LoginInteractive(context.Background(), nil, nil) require.NoError(t, err) require.IsType(t, new(azdCredential), cred) diff --git a/go.mod b/go.mod index 9b238e6353f..1780d794a4b 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v0.13.0 github.com/Azure/azure-storage-file-go v0.8.0 - github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 + github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/benbjohnson/clock v1.3.0 github.com/blang/semver/v4 v4.0.0 @@ -60,6 +60,8 @@ require github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/arma require github.com/adam-lavrik/go-imath v0.0.0-20210910152346-265a42a96f0b +require github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + require ( github.com/Azure/azure-pipeline-go v0.2.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect @@ -71,7 +73,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index 3452abd2ab5..e1c255e2353 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028g github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= github.com/Azure/azure-storage-file-go v0.8.0 h1:OX8DGsleWLUE6Mw4R/OeWEZMvsTIpwN94J59zqKQnTI= github.com/Azure/azure-storage-file-go v0.8.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c= -github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= -github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -193,8 +193,10 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=