github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/user/login.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package user
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/api/usermanager"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/cmd/modelcmd"
    16  	"github.com/juju/juju/juju"
    17  	"github.com/juju/juju/jujuclient"
    18  )
    19  
    20  const loginDoc = `
    21  After login, a token ("macaroon") will become active. It has an expiration
    22  time of 24 hours. Upon expiration, no further Juju commands can be issued
    23  and the user will be prompted to log in again.
    24  
    25  Examples:
    26  
    27      juju login bob
    28  
    29  See also:
    30      disable-user
    31      enable-user
    32      logout
    33  
    34  `
    35  
    36  // NewLoginCommand returns a new cmd.Command to handle "juju login".
    37  func NewLoginCommand() cmd.Command {
    38  	return modelcmd.WrapController(&loginCommand{
    39  		newLoginAPI: func(args juju.NewAPIConnectionParams) (LoginAPI, ConnectionAPI, error) {
    40  			api, err := juju.NewAPIConnection(args)
    41  			if err != nil {
    42  				return nil, nil, errors.Trace(err)
    43  			}
    44  			return usermanager.NewClient(api), api, nil
    45  		},
    46  	})
    47  }
    48  
    49  // loginCommand changes the password for a user.
    50  type loginCommand struct {
    51  	modelcmd.ControllerCommandBase
    52  	newLoginAPI func(juju.NewAPIConnectionParams) (LoginAPI, ConnectionAPI, error)
    53  	User        string
    54  }
    55  
    56  // Info implements Command.Info.
    57  func (c *loginCommand) Info() *cmd.Info {
    58  	return &cmd.Info{
    59  		Name:    "login",
    60  		Args:    "[username]",
    61  		Purpose: "Logs a user in to a controller.",
    62  		Doc:     loginDoc,
    63  	}
    64  }
    65  
    66  // Init implements Command.Init.
    67  func (c *loginCommand) Init(args []string) error {
    68  	var err error
    69  	c.User, err = cmd.ZeroOrOneArgs(args)
    70  	if err != nil {
    71  		return errors.Trace(err)
    72  	}
    73  	return nil
    74  }
    75  
    76  // LoginAPI provides the API methods that the login command uses.
    77  type LoginAPI interface {
    78  	Close() error
    79  }
    80  
    81  // ConnectionAPI provides relevant API methods off the underlying connection.
    82  type ConnectionAPI interface {
    83  	AuthTag() names.Tag
    84  	ControllerAccess() string
    85  }
    86  
    87  // Run implements Command.Run.
    88  func (c *loginCommand) Run(ctx *cmd.Context) error {
    89  	controllerName := c.ControllerName()
    90  	store := c.ClientStore()
    91  	accountDetails, err := store.AccountDetails(controllerName)
    92  	if err != nil && !errors.IsNotFound(err) {
    93  		return errors.Trace(err)
    94  	}
    95  
    96  	user := c.User
    97  	if user == "" && accountDetails == nil {
    98  		// The username has not been specified, and there
    99  		// is no current account. See if the user can log
   100  		// in with macaroons.
   101  		args, err := c.NewAPIConnectionParams(
   102  			store, controllerName, "",
   103  			&jujuclient.AccountDetails{},
   104  		)
   105  		if err != nil {
   106  			return errors.Trace(err)
   107  		}
   108  		api, conn, err := c.newLoginAPI(args)
   109  		if err == nil {
   110  			authTag := conn.AuthTag()
   111  			api.Close()
   112  			ctx.Infof("You are now logged in to %q as %q.", controllerName, authTag.Id())
   113  			return nil
   114  		}
   115  		if !params.IsCodeNoCreds(err) {
   116  			return errors.Annotate(err, "creating API connection")
   117  		}
   118  		// CodeNoCreds was returned, which means that external
   119  		// users are not supported. Fall back to prompting the
   120  		// user for their username and password.
   121  	}
   122  
   123  	if user == "" {
   124  		// The username has not been specified, so prompt for it.
   125  		fmt.Fprint(ctx.Stderr, "username: ")
   126  		var err error
   127  		user, err = readLine(ctx.Stdin)
   128  		if err != nil {
   129  			return errors.Trace(err)
   130  		}
   131  		if user == "" {
   132  			return errors.Errorf("you must specify a username")
   133  		}
   134  	}
   135  	if !names.IsValidUserName(user) {
   136  		return errors.NotValidf("user name %q", user)
   137  	}
   138  	userTag := names.NewUserTag(user)
   139  
   140  	// Make sure that the client is not already logged in,
   141  	// or if it is, that it is logged in as the specified
   142  	// user.
   143  	if accountDetails != nil && accountDetails.User != userTag.Canonical() {
   144  		return errors.New(`already logged in
   145  
   146  Run "juju logout" first before attempting to log in as a different user.
   147  `)
   148  	}
   149  
   150  	// Log in without specifying a password in the account details. This
   151  	// will trigger macaroon-based authentication, which will prompt the
   152  	// user for their password.
   153  	accountDetails = &jujuclient.AccountDetails{
   154  		User: userTag.Canonical(),
   155  	}
   156  	params, err := c.NewAPIConnectionParams(store, controllerName, "", accountDetails)
   157  	if err != nil {
   158  		return errors.Trace(err)
   159  	}
   160  	api, conn, err := c.newLoginAPI(params)
   161  	if err != nil {
   162  		return errors.Annotate(err, "creating API connection")
   163  	}
   164  	defer api.Close()
   165  
   166  	accountDetails.LastKnownAccess = conn.ControllerAccess()
   167  	if err := store.UpdateAccount(controllerName, *accountDetails); err != nil {
   168  		return errors.Annotate(err, "failed to record temporary credential")
   169  	}
   170  	ctx.Infof("You are now logged in to %q as %q.", controllerName, userTag.Canonical())
   171  	return nil
   172  }