From f26d0a88d66fc615a88a9b600b0658d748914d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Gustav=20Str=C3=A5b=C3=B8?= <65334626+nilsgstrabo@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:51:17 +0200 Subject: [PATCH] Replace device code login with interactive login (#90) * replace device code login with interactive login * add flag --use-device-code for login command * add SilenceUsage and fix usage for flag * extend help for use-device-code * update help --- cmd/login.go | 6 ++++-- pkg/client/auth/client.go | 5 +---- pkg/client/auth/msal_provider.go | 34 ++++++++++++++++++++++++-------- pkg/client/client.go | 8 ++++---- pkg/flagnames/names.go | 1 + 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/cmd/login.go b/cmd/login.go index 44eb25e..203a782 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -16,6 +16,7 @@ package cmd import ( "github.com/equinor/radix-cli/pkg/client" + "github.com/equinor/radix-cli/pkg/flagnames" "github.com/spf13/cobra" ) @@ -25,10 +26,10 @@ var loginCmd = &cobra.Command{ Short: "Login to Radix", Long: `Login to Radix.`, RunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceUsage = true - err := client.LoginCommand(cmd) + useDeviceCode, _ := cmd.Flags().GetBool(flagnames.UseDeviceCode) + err := client.LoginCommand(cmd, useDeviceCode) if err != nil { return err } @@ -39,5 +40,6 @@ var loginCmd = &cobra.Command{ func init() { rootCmd.AddCommand(loginCmd) + loginCmd.Flags().Bool(flagnames.UseDeviceCode, false, "Use CLI's old authentication flow based on device code. The device code flow does not work for compliant device policy enabled accounts.") setVerbosePersistentFlag(loginCmd) } diff --git a/pkg/client/auth/client.go b/pkg/client/auth/client.go index 7b8ffe2..d9c2327 100644 --- a/pkg/client/auth/client.go +++ b/pkg/client/auth/client.go @@ -1,17 +1,14 @@ package auth import ( - "fmt" - "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" radixconfig "github.com/equinor/radix-cli/pkg/config" ) // newPublicClient creates a new authentication client -func newPublicClient(radixConfig *radixconfig.RadixConfig, clientID, tenantID string) (*public.Client, error) { +func newPublicClient(radixConfig *radixconfig.RadixConfig, clientID, authority string) (*public.Client, error) { cacheAccessor := NewTokenCache(radixConfig) cache := public.WithCache(cacheAccessor) - authority := fmt.Sprintf("https://login.microsoftonline.com/%s", tenantID) client, err := public.New(clientID, cache, public.WithAuthority(authority)) if err != nil { return nil, err diff --git a/pkg/client/auth/msal_provider.go b/pkg/client/auth/msal_provider.go index 6c65294..8644c58 100644 --- a/pkg/client/auth/msal_provider.go +++ b/pkg/client/auth/msal_provider.go @@ -13,24 +13,27 @@ import ( // MSALAuthProvider is an AuthProvider that uses MSAL type MSALAuthProvider interface { - Login(ctx context.Context) error + Login(ctx context.Context, useDeviceCode bool) error Logout(ctx context.Context) error runtime.ClientAuthInfoWriter } // NewMSALAuthProvider creates a new MSALAuthProvider func NewMSALAuthProvider(radixConfig *radixconfig.RadixConfig, clientID, tenantID string) (MSALAuthProvider, error) { - client, err := newPublicClient(radixConfig, clientID, tenantID) + authority := fmt.Sprintf("https://login.microsoftonline.com/%s", tenantID) + client, err := newPublicClient(radixConfig, clientID, authority) if err != nil { return nil, err } return &msalAuthProvider{ - client: client, + client: client, + authority: authority, }, nil } type msalAuthProvider struct { - client *public.Client + authority string + client *public.Client } func (provider *msalAuthProvider) AuthenticateRequest(r runtime.ClientRequest, _ strfmt.Registry) error { @@ -43,8 +46,12 @@ func (provider *msalAuthProvider) AuthenticateRequest(r runtime.ClientRequest, _ // Login allows the plugin to initialize its configuration. It must not // require direct user interaction. -func (provider *msalAuthProvider) Login(ctx context.Context) error { - _, err := provider.loginWithDeviceCode(ctx) +func (provider *msalAuthProvider) Login(ctx context.Context, useDeviceCode bool) error { + var loginCmd func(context.Context) (string, error) = provider.loginInteractive + if useDeviceCode { + loginCmd = provider.loginDeviceCode + } + _, err := loginCmd(ctx) return err } @@ -80,10 +87,21 @@ func (provider *msalAuthProvider) GetToken(ctx context.Context) (string, error) // either there was no cached account/token or the call to AcquireTokenSilent() failed // make a new request to AAD - return provider.loginWithDeviceCode(ctx) + return provider.loginInteractive(ctx) +} + +func (provider *msalAuthProvider) loginInteractive(ctx context.Context) (string, error) { + ctx, cancel := context.WithTimeout(ctx, 100*time.Second) + defer cancel() + fmt.Printf("A web browser has been opened at %s/oauth2/v2.0/authorize. Please continue the login in the web browser.\n", provider.authority) + result, err := provider.client.AcquireTokenInteractive(ctx, getScopes()) + if err != nil { + return "", err + } + return result.AccessToken, nil } -func (provider *msalAuthProvider) loginWithDeviceCode(ctx context.Context) (string, error) { +func (provider *msalAuthProvider) loginDeviceCode(ctx context.Context) (string, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Second) defer cancel() devCode, err := provider.client.AcquireTokenByDeviceCode(ctx, getScopes()) diff --git a/pkg/client/client.go b/pkg/client/client.go index a546b9f..a31a805 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -84,8 +84,8 @@ func getAuthWriter(cmd *cobra.Command, config *radixconfig.RadixConfig) (runtime } // LoginCommand Login client for command -func LoginCommand(cmd *cobra.Command) error { - return LoginContext() +func LoginCommand(cmd *cobra.Command, useDeviceCode bool) error { + return LoginContext(useDeviceCode) } // LogoutCommand Logout command @@ -112,7 +112,7 @@ func getContextAndCluster(cmd *cobra.Command) (string, string, error) { } // LoginContext Performs login -func LoginContext() error { +func LoginContext(useDeviceCode bool) error { radixConfig, err := radixconfig.GetRadixConfig() if err != nil { return err @@ -124,7 +124,7 @@ func LoginContext() error { if err != nil { return err } - return provider.Login(context.Background()) + return provider.Login(context.Background(), useDeviceCode) } func getAuthProvider(radixConfig *radixconfig.RadixConfig) (auth.MSALAuthProvider, error) { diff --git a/pkg/flagnames/names.go b/pkg/flagnames/names.go index 65db67b..0109c63 100644 --- a/pkg/flagnames/names.go +++ b/pkg/flagnames/names.go @@ -39,6 +39,7 @@ const ( TokenEnvironment = "token-environment" TokenStdin = "token-stdin" UseActiveDeployment = "use-active-deployment" + UseDeviceCode = "use-device-code" User = "user" Variable = "variable" Verbose = "verbose"