From d760c9eb49c9d256bc72d8aa08b63c814c21753b Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Tue, 12 Jul 2022 11:35:56 -0700 Subject: [PATCH 1/2] Improve login detection - Fix internal #1392 : Location prompt now first attempts `az login` - Fix #10: Update error message to include `run azd login` - Fix #48: Detect when refresh token is expired and run automatic login --- cli/azd/cmd/login.go | 2 +- cli/azd/cmd/util.go | 5 +++++ cli/azd/pkg/tools/azcli.go | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cli/azd/cmd/login.go b/cli/azd/cmd/login.go index 8b8194abe74..a9a974abad0 100644 --- a/cli/azd/cmd/login.go +++ b/cli/azd/cmd/login.go @@ -93,7 +93,7 @@ func (la *loginAction) SetupFlags(persistent *pflag.FlagSet, local *pflag.FlagSe func ensureLoggedIn(ctx context.Context) error { azCli := commands.GetAzCliFromContext(ctx) _, err := azCli.GetAccessToken(ctx) - if errors.Is(err, tools.ErrAzCliNotLoggedIn) { + if errors.Is(err, tools.ErrAzCliNotLoggedIn) || errors.Is(err, tools.ErrAzCliRefreshTokenExpired) { if err := runLogin(ctx, false); err != nil { return fmt.Errorf("logging in: %w", err) } diff --git a/cli/azd/cmd/util.go b/cli/azd/cmd/util.go index 679b87aa508..5b46e437936 100644 --- a/cli/azd/cmd/util.go +++ b/cli/azd/cmd/util.go @@ -467,7 +467,12 @@ func promptTemplate(ctx context.Context, message string, askOne Asker) (string, // promptLocation asks the user to select a location from a list of supported azure location func promptLocation(ctx context.Context, message string, askOne Asker) (string, error) { + if err := ensureLoggedIn(ctx); err != nil { + return "", fmt.Errorf("listing locations: %w", err) + } + azCli := commands.GetAzCliFromContext(ctx) + locations, err := azCli.ListAccountLocations(ctx) if err != nil { return "", fmt.Errorf("listing locations: %w", err) diff --git a/cli/azd/pkg/tools/azcli.go b/cli/azd/pkg/tools/azcli.go index ea3a0778f89..e23def762a8 100644 --- a/cli/azd/pkg/tools/azcli.go +++ b/cli/azd/pkg/tools/azcli.go @@ -21,7 +21,8 @@ import ( ) var ( - ErrAzCliNotLoggedIn = errors.New("cli is not logged in") + ErrAzCliNotLoggedIn = errors.New("cli is not logged in. Try running \"azd login\" to fix") + ErrAzCliRefreshTokenExpired = errors.New("refresh token has expired. Try running \"azd login\" to fix") ErrCurrentPrincipalIsNotUser = errors.New("current principal is not a user principal") ErrClientAssertionExpired = errors.New("client assertion expired") ErrDeploymentNotFound = errors.New("deployment not found") @@ -782,6 +783,8 @@ func (cli *azCli) GetAccessToken(ctx context.Context) (AzCliAccessToken, error) res, err := cli.runAzCommand(ctx, "account", "get-access-token", "--output", "json") if isNotLoggedInMessage(res.Stderr) { return AzCliAccessToken{}, ErrAzCliNotLoggedIn + } else if isRefreshTokenExpiredMessage(res.Stderr) { + return AzCliAccessToken{}, ErrAzCliRefreshTokenExpired } else if err != nil { return AzCliAccessToken{}, fmt.Errorf("failed running az account get-access-token: %s: %w", res.String(), err) } @@ -900,7 +903,13 @@ func (cli *azCli) runAzCommandWithArgs(ctx context.Context, args executil.RunArg return cli.runWithResultFn(ctx, args) } +// Azure Active Directory codes can be referenced via https://login.microsoftonline.com/error?code=, +// where ERROR_CODE is the digits portion of an AAD error code. Example: AADSTS70043 has error code 70043 + var isNotLoggedInMessageRegex = regexp.MustCompile(`Please run ('|")az login('|") to (setup account|access your accounts)\.`) + +// AADSTS70043: The refresh token has expired or is invalid due to sign-in frequency checks by conditional access. +var isRefreshTokenExpiredMessageRegex = regexp.MustCompile(`AADSTS70043`) var isResourceSegmentMeNotFoundMessageRegex = regexp.MustCompile(`Resource not found for the segment 'me'.`) var isDeploymentNotFoundMessageRegex = regexp.MustCompile(`ERROR: \(DeploymentNotFound\)`) var isClientAssertionInvalidMessagedRegex = regexp.MustCompile(`ERROR: AADSTS700024: Client assertion is not within its valid time range.`) @@ -911,6 +920,10 @@ func isNotLoggedInMessage(s string) bool { return isNotLoggedInMessageRegex.MatchString(s) } +func isRefreshTokenExpiredMessage(s string) bool { + return isRefreshTokenExpiredMessageRegex.MatchString(s) +} + func isResourceSegmentMeNotFoundMessage(s string) bool { return isResourceSegmentMeNotFoundMessageRegex.MatchString(s) } From d8869f53164f0bcf15051c589fada1196df52381 Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Tue, 12 Jul 2022 15:21:14 -0700 Subject: [PATCH 2/2] Move `ensureLoggedIn` check up closer to root --- cli/azd/cmd/util.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cli/azd/cmd/util.go b/cli/azd/cmd/util.go index 5b46e437936..b74c4b6c6b7 100644 --- a/cli/azd/cmd/util.go +++ b/cli/azd/cmd/util.go @@ -172,6 +172,12 @@ func ensureEnvironmentInitialized(ctx context.Context, environmentName string, e env.SetEnvName(environmentName) } + if !hasSubID || !hasPrincipalID || !hasLocation { + if err := ensureLoggedIn(ctx); err != nil { + return fmt.Errorf("logging in: %w", err) + } + } + if !hasLocation { var location string location, err := promptLocation(ctx, "Please select an Azure location to use:", askOne) @@ -182,12 +188,6 @@ func ensureEnvironmentInitialized(ctx context.Context, environmentName string, e env.Values[environment.LocationEnvVarName] = strings.TrimSpace(location) } - if !hasSubID || !hasPrincipalID { - if err := ensureLoggedIn(ctx); err != nil { - return fmt.Errorf("logging in: %w", err) - } - } - azCli := commands.GetAzCliFromContext(ctx) if !hasSubID { @@ -467,10 +467,6 @@ func promptTemplate(ctx context.Context, message string, askOne Asker) (string, // promptLocation asks the user to select a location from a list of supported azure location func promptLocation(ctx context.Context, message string, askOne Asker) (string, error) { - if err := ensureLoggedIn(ctx); err != nil { - return "", fmt.Errorf("listing locations: %w", err) - } - azCli := commands.GetAzCliFromContext(ctx) locations, err := azCli.ListAccountLocations(ctx)