Skip to content
Open
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
8 changes: 8 additions & 0 deletions cli/azd/cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@ func authActions(root *actions.ActionDescriptor) *actions.ActionDescriptor {
ActionResolver: newLogoutAction,
})

group.Add("status", &actions.ActionDescriptorOptions{
Command: newAuthStatusCmd(),
FlagsResolver: newAuthStatusFlags,
ActionResolver: newAuthStatusAction,
OutputFormats: []output.Format{output.JsonFormat, output.NoneFormat},
DefaultFormat: output.NoneFormat,
})

return group
}
34 changes: 5 additions & 29 deletions cli/azd/cmd/auth_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,36 +322,12 @@ func (la *loginAction) Run(ctx context.Context) (*actions.ActionResult, error) {
res.ExpiresOn = &token.ExpiresOn
}

if la.formatter.Kind() != output.NoneFormat {
return nil, la.formatter.Format(res, la.writer, nil)
} else {
var msg string
switch res.Status {
case contracts.LoginStatusSuccess:
msg = "Logged in to Azure"
case contracts.LoginStatusUnauthenticated:
msg = "Not logged in, run `azd auth login` to login to Azure"
default:
panic("Unhandled login status")
}

// get user account information - login --check-status
details, err := la.authManager.LogInDetails(ctx)

// error getting user account or not logged in
if err != nil {
log.Printf("error: getting signed in account: %v", err)
fmt.Fprintln(la.console.Handles().Stdout, msg)
return nil, nil
}

// only print the message if the user is logged in
la.console.MessageUxItem(ctx, &ux.LoggedIn{
LoggedInAs: details.Account,
LoginType: ux.LoginType(details.LoginType),
})
return nil, nil
err = displayAuthStatus(ctx, la.formatter, la.writer, la.console, la.authManager, res)
if err != nil {
return nil, err
}

return nil, nil
}

if err := la.login(ctx); err != nil {
Expand Down
127 changes: 127 additions & 0 deletions cli/azd/cmd/auth_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"context"
"errors"
"fmt"
"io"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/contracts"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

type authStatusFlags struct {
global *internal.GlobalCommandOptions
}

func newAuthStatusFlags(cmd *cobra.Command, global *internal.GlobalCommandOptions) *authStatusFlags {
flags := &authStatusFlags{}
flags.Bind(cmd.Flags(), global)
return flags
}

func (f *authStatusFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandOptions) {
f.global = global
}

func newAuthStatusCmd() *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "Check the authentication status.",
Long: "Check the authentication status. Returns information about the logged-in user and when credentials expire.",
}
}

// authStatusAuthManager defines the interface needed from auth.Manager for status checking
type authStatusAuthManager interface {
CredentialForCurrentUser(
ctx context.Context,
options *auth.CredentialForCurrentUserOptions,
) (azcore.TokenCredential, error)
LoginScopes() []string
LogInDetails(ctx context.Context) (*auth.LogInDetails, error)
}

type authStatusAction struct {
formatter output.Formatter
writer io.Writer
console input.Console
authManager authStatusAuthManager
flags *authStatusFlags
}

func newAuthStatusAction(
formatter output.Formatter,
writer io.Writer,
authManager *auth.Manager,
flags *authStatusFlags,
console input.Console,
) actions.Action {
return &authStatusAction{
formatter: formatter,
writer: writer,
console: console,
authManager: authManager,
flags: flags,
}
}

func (a *authStatusAction) Run(ctx context.Context) (*actions.ActionResult, error) {
scopes := a.authManager.LoginScopes()

// The status command prints the authentication status to stdout and returns a zero exit code.
// Non-setup related errors are printed to stderr.
token, err := a.verifyLoggedIn(ctx, scopes)
var loginExpiryError *auth.ReLoginRequiredError
if err != nil &&
!errors.Is(err, auth.ErrNoCurrentUser) &&
!errors.As(err, &loginExpiryError) {
fmt.Fprintln(a.console.Handles().Stderr, err.Error())
}

res := contracts.LoginResult{}
if err != nil {
res.Status = contracts.LoginStatusUnauthenticated
} else {
res.Status = contracts.LoginStatusSuccess
res.ExpiresOn = &token.ExpiresOn
}

err = displayAuthStatus(ctx, a.formatter, a.writer, a.console, a.authManager, res)
if err != nil {
return nil, err
}

return nil, nil
}

// Verifies that the user has credentials stored,
// and that the credentials stored is accepted by the identity server (can be exchanged for access token).
func (a *authStatusAction) verifyLoggedIn(ctx context.Context, scopes []string) (*azcore.AccessToken, error) {
cred, err := a.authManager.CredentialForCurrentUser(ctx, nil)
if err != nil {
return nil, err
}

// Ensure credential is valid, and can be exchanged for an access token
token, err := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: scopes,
})

if err != nil {
return nil, err
}

return &token, nil
}
Loading
Loading