github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"github.com/juju/names"
    12  	"gopkg.in/macaroon.v1"
    13  
    14  	"github.com/juju/juju/api/usermanager"
    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  Log in to the controller.
    22  
    23  After logging in successfully, the client system will
    24  be updated such that any previously recorded password
    25  will be removed from disk, and a temporary, time-limited
    26  credential written in its place. Once the credential
    27  expires, you will be prompted to run "juju login" again.
    28  
    29  Examples:
    30    # Log in as the current user for the controller.
    31    juju login
    32  
    33    # Log in as the user "bob".
    34    juju login bob
    35  
    36  `
    37  
    38  // NewLoginCommand returns a new cmd.Command to handle "juju login".
    39  func NewLoginCommand() cmd.Command {
    40  	return modelcmd.WrapController(&loginCommand{
    41  		newLoginAPI: func(args juju.NewAPIConnectionParams) (LoginAPI, error) {
    42  			api, err := juju.NewAPIConnection(args)
    43  			if err != nil {
    44  				return nil, errors.Trace(err)
    45  			}
    46  			return usermanager.NewClient(api), nil
    47  		},
    48  	})
    49  }
    50  
    51  // loginCommand changes the password for a user.
    52  type loginCommand struct {
    53  	modelcmd.ControllerCommandBase
    54  	newLoginAPI func(juju.NewAPIConnectionParams) (LoginAPI, error)
    55  	User        string
    56  }
    57  
    58  // Info implements Command.Info.
    59  func (c *loginCommand) Info() *cmd.Info {
    60  	return &cmd.Info{
    61  		Name:    "login",
    62  		Args:    "[username]",
    63  		Purpose: "logs in to the controller",
    64  		Doc:     loginDoc,
    65  	}
    66  }
    67  
    68  // Init implements Command.Init.
    69  func (c *loginCommand) Init(args []string) error {
    70  	var err error
    71  	c.User, err = cmd.ZeroOrOneArgs(args)
    72  	if err != nil {
    73  		return errors.Trace(err)
    74  	}
    75  	return nil
    76  }
    77  
    78  // LoginAPI provides the API methods that the login command uses.
    79  type LoginAPI interface {
    80  	CreateLocalLoginMacaroon(names.UserTag) (*macaroon.Macaroon, error)
    81  	Close() error
    82  }
    83  
    84  // Run implements Command.Run.
    85  func (c *loginCommand) Run(ctx *cmd.Context) error {
    86  	controllerName := c.ControllerName()
    87  	store := c.ClientStore()
    88  
    89  	user := c.User
    90  	if user == "" {
    91  		// The username has not been specified, so prompt for it.
    92  		fmt.Fprint(ctx.Stderr, "username: ")
    93  		var err error
    94  		user, err = readLine(ctx.Stdin)
    95  		if err != nil {
    96  			return errors.Trace(err)
    97  		}
    98  		if user == "" {
    99  			return errors.Errorf("you must specify a username")
   100  		}
   101  	}
   102  	if !names.IsValidUserName(user) {
   103  		return errors.NotValidf("user name %q", user)
   104  	}
   105  	userTag := names.NewUserTag(user)
   106  	accountName := userTag.Canonical()
   107  
   108  	// Make sure that the client is not already logged in,
   109  	// or if it is, that it is logged in as the specified
   110  	// user.
   111  	currentAccountName, err := store.CurrentAccount(controllerName)
   112  	if err == nil {
   113  		if currentAccountName != accountName {
   114  			return errors.New(`already logged in
   115  
   116  Run "juju logout" first before attempting to log in as a different user.
   117  `)
   118  		}
   119  	} else if !errors.IsNotFound(err) {
   120  		return errors.Trace(err)
   121  	}
   122  	accountDetails, err := store.AccountByName(controllerName, accountName)
   123  	if err != nil && !errors.IsNotFound(err) {
   124  		return errors.Trace(err)
   125  	}
   126  
   127  	// Read password from the terminal, and attempt to log in using that.
   128  	fmt.Fprint(ctx.Stderr, "password: ")
   129  	password, err := readPassword(ctx.Stdin)
   130  	fmt.Fprintln(ctx.Stderr)
   131  	if err != nil {
   132  		return errors.Trace(err)
   133  	}
   134  	params, err := c.NewAPIConnectionParams(store, controllerName, "", "")
   135  	if err != nil {
   136  		return errors.Trace(err)
   137  	}
   138  	if accountDetails != nil {
   139  		accountDetails.Password = password
   140  	} else {
   141  		accountDetails = &jujuclient.AccountDetails{
   142  			User:     accountName,
   143  			Password: password,
   144  		}
   145  	}
   146  	params.AccountDetails = accountDetails
   147  	api, err := c.newLoginAPI(params)
   148  	if err != nil {
   149  		return errors.Annotate(err, "creating API connection")
   150  	}
   151  	defer api.Close()
   152  
   153  	// Create a new local login macaroon, and update the account details
   154  	// in the client store, removing the recorded password (if any) and
   155  	// storing the macaroon.
   156  	macaroon, err := api.CreateLocalLoginMacaroon(userTag)
   157  	if err != nil {
   158  		return errors.Annotate(err, "failed to create a temporary credential")
   159  	}
   160  	macaroonJSON, err := macaroon.MarshalJSON()
   161  	if err != nil {
   162  		return errors.Annotate(err, "marshalling temporary credential to JSON")
   163  	}
   164  	accountDetails.Password = ""
   165  	accountDetails.Macaroon = string(macaroonJSON)
   166  	if err := store.UpdateAccount(controllerName, accountName, *accountDetails); err != nil {
   167  		return errors.Annotate(err, "failed to record temporary credential")
   168  	}
   169  	if err := store.SetCurrentAccount(controllerName, accountName); err != nil {
   170  		return errors.Annotate(err, "failed to set current account")
   171  	}
   172  	ctx.Infof("You are now logged in to %q as %q.", controllerName, accountName)
   173  	return nil
   174  }