github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/user/login_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package user_test
     5  
     6  import (
     7  	"bytes"
     8  	"strings"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/api"
    17  	apibase "github.com/juju/juju/api/base"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/juju/user"
    20  	"github.com/juju/juju/cmd/modelcmd"
    21  	"github.com/juju/juju/juju"
    22  	"github.com/juju/juju/jujuclient"
    23  )
    24  
    25  type LoginCommandSuite struct {
    26  	BaseSuite
    27  	apiConnection *loginMockAPI
    28  
    29  	// apiConnectionParams is set when the mock newAPIConnection
    30  	// implementation installed by SetUpTest is called.
    31  	apiConnectionParams juju.NewAPIConnectionParams
    32  }
    33  
    34  var _ = gc.Suite(&LoginCommandSuite{})
    35  
    36  func (s *LoginCommandSuite) SetUpTest(c *gc.C) {
    37  	s.BaseSuite.SetUpTest(c)
    38  	s.apiConnection = &loginMockAPI{
    39  		controllerTag:    names.NewControllerTag(mockControllerUUID),
    40  		authTag:          names.NewUserTag("user@external"),
    41  		controllerAccess: "superuser",
    42  	}
    43  	s.apiConnectionParams = juju.NewAPIConnectionParams{}
    44  	s.PatchValue(user.NewAPIConnection, func(p juju.NewAPIConnectionParams) (api.Connection, error) {
    45  		// The account details are modified in place, so take a copy.
    46  		accountDetails := *p.AccountDetails
    47  		p.AccountDetails = &accountDetails
    48  		s.apiConnectionParams = p
    49  		return s.apiConnection, nil
    50  	})
    51  	s.PatchValue(user.ListModels, func(c api.Connection, userName string) ([]apibase.UserModel, error) {
    52  		return nil, nil
    53  	})
    54  	s.PatchValue(user.APIOpen, func(c *modelcmd.CommandBase, info *api.Info, opts api.DialOpts) (api.Connection, error) {
    55  		return s.apiConnection, nil
    56  	})
    57  	s.PatchValue(user.LoginClientStore, s.store)
    58  }
    59  
    60  func (s *LoginCommandSuite) TestInitError(c *gc.C) {
    61  	for i, test := range []struct {
    62  		args   []string
    63  		stderr string
    64  	}{{
    65  		args:   []string{"--foobar"},
    66  		stderr: `ERROR option provided but not defined: --foobar\n`,
    67  	}, {
    68  		args:   []string{"foobar", "extra"},
    69  		stderr: `ERROR unrecognized args: \["extra"\]\n`,
    70  	}} {
    71  		c.Logf("test %d", i)
    72  		stdout, stderr, code := runLogin(c, "", test.args...)
    73  		c.Check(stdout, gc.Equals, "")
    74  		c.Check(stderr, gc.Matches, test.stderr)
    75  		c.Assert(code, gc.Equals, 2)
    76  	}
    77  }
    78  
    79  func (s *LoginCommandSuite) TestLogin(c *gc.C) {
    80  	// When we run login with a current controller,
    81  	// it will just verify that we can log in, leave
    82  	// every unchanged and print nothing.
    83  	stdout, stderr, code := runLogin(c, "")
    84  	c.Check(code, gc.Equals, 0)
    85  	c.Check(stdout, gc.Equals, "")
    86  	c.Check(stderr, gc.Equals, "")
    87  	s.assertStorePassword(c, "current-user", "old-password", "superuser")
    88  	c.Assert(s.apiConnectionParams.AccountDetails, jc.DeepEquals, &jujuclient.AccountDetails{
    89  		User:     "current-user",
    90  		Password: "old-password",
    91  	})
    92  }
    93  
    94  func (s *LoginCommandSuite) TestLoginNewUser(c *gc.C) {
    95  	err := s.store.RemoveAccount("testing")
    96  	c.Assert(err, jc.ErrorIsNil)
    97  	stdout, stderr, code := runLogin(c, "", "-u", "new-user")
    98  	c.Check(stdout, gc.Equals, "")
    99  	c.Check(stderr, gc.Matches, `
   100  Welcome, new-user. You are now logged into "testing".
   101  
   102  There are no models available(.|\n)*`[1:])
   103  	c.Assert(code, gc.Equals, 0)
   104  	s.assertStorePassword(c, "new-user", "", "superuser")
   105  	c.Assert(s.apiConnectionParams.AccountDetails, jc.DeepEquals, &jujuclient.AccountDetails{
   106  		User: "new-user",
   107  	})
   108  }
   109  
   110  func (s *LoginCommandSuite) TestLoginAlreadyLoggedInSameUser(c *gc.C) {
   111  	stdout, stderr, code := runLogin(c, "", "-u", "current-user")
   112  	c.Check(stdout, gc.Equals, "")
   113  	c.Check(stderr, gc.Equals, "")
   114  	c.Assert(code, gc.Equals, 0)
   115  }
   116  
   117  func (s *LoginCommandSuite) TestLoginWithOneAvailableModel(c *gc.C) {
   118  	s.PatchValue(user.ListModels, func(c api.Connection, userName string) ([]apibase.UserModel, error) {
   119  		return []apibase.UserModel{{
   120  			Name:  "foo",
   121  			UUID:  "some-uuid",
   122  			Owner: "bob",
   123  			Type:  "iaas",
   124  		}}, nil
   125  	})
   126  	err := s.store.RemoveAccount("testing")
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	stdout, stderr, code := runLogin(c, "")
   129  	c.Assert(code, gc.Equals, 0)
   130  	c.Check(stdout, gc.Equals, "")
   131  	c.Check(stderr, gc.Matches, `Welcome, user@external. You are now logged into "testing".
   132  
   133  Current model set to "bob/foo".
   134  `)
   135  }
   136  
   137  func (s *LoginCommandSuite) TestLoginWithSeveralAvailableModels(c *gc.C) {
   138  	s.PatchValue(user.ListModels, func(c api.Connection, userName string) ([]apibase.UserModel, error) {
   139  		return []apibase.UserModel{{
   140  			Name:  "foo",
   141  			UUID:  "some-uuid",
   142  			Owner: "bob",
   143  			Type:  "iaas",
   144  		}, {
   145  			Name:  "bar",
   146  			UUID:  "some-uuid",
   147  			Owner: "alice",
   148  			Type:  "iaas",
   149  		}}, nil
   150  	})
   151  	err := s.store.RemoveAccount("testing")
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	stdout, stderr, code := runLogin(c, "")
   154  	c.Assert(code, gc.Equals, 0)
   155  	c.Check(stdout, gc.Equals, "")
   156  	c.Check(stderr, gc.Matches, `Welcome, user@external. You are now logged into "testing".
   157  
   158  There are 2 models available. Use "juju switch" to select
   159  one of them:
   160    - juju switch alice/bar
   161    - juju switch bob/foo
   162  `)
   163  }
   164  
   165  func (s *LoginCommandSuite) TestLoginWithNonExistentController(c *gc.C) {
   166  	stdout, stderr, code := runLogin(c, "", "-c", "something")
   167  	c.Assert(code, gc.Equals, 1)
   168  	c.Check(stdout, gc.Equals, "")
   169  	c.Check(stderr, gc.Matches, `ERROR controller "something" does not exist\n`)
   170  }
   171  
   172  func (s *LoginCommandSuite) TestLoginWithNoCurrentController(c *gc.C) {
   173  	s.store.CurrentControllerName = ""
   174  	stdout, stderr, code := runLogin(c, "")
   175  	c.Assert(code, gc.Equals, 1)
   176  	c.Check(stdout, gc.Equals, "")
   177  	c.Check(stderr, gc.Matches, `ERROR no current controller\n`)
   178  }
   179  
   180  func (s *LoginCommandSuite) TestLoginAlreadyLoggedInDifferentUser(c *gc.C) {
   181  	stdout, stderr, code := runLogin(c, "", "-u", "other-user")
   182  	c.Check(stdout, gc.Equals, "")
   183  	c.Check(stderr, gc.Equals, `
   184  ERROR cannot log into controller "testing": already logged in as current-user.
   185  
   186  Run "juju logout" first before attempting to log in as a different user.
   187  `[1:])
   188  	c.Assert(code, gc.Equals, 1)
   189  }
   190  
   191  func (s *LoginCommandSuite) TestLoginWithExistingInvalidPassword(c *gc.C) {
   192  	call := 0
   193  	*user.NewAPIConnection = func(p juju.NewAPIConnectionParams) (api.Connection, error) {
   194  		call++
   195  		switch call {
   196  		case 1:
   197  			// First time: try to log in with existing details.
   198  			c.Check(p.AccountDetails.User, gc.Equals, "current-user")
   199  			c.Check(p.AccountDetails.Password, gc.Equals, "old-password")
   200  			return nil, errors.Unauthorizedf("cannot login with that silly old password")
   201  		case 2:
   202  			// Second time: try external-user auth.
   203  			c.Check(p.AccountDetails.User, gc.Equals, "")
   204  			c.Check(p.AccountDetails.Password, gc.Equals, "")
   205  			return nil, params.Error{
   206  				Code:    params.CodeNoCreds,
   207  				Message: params.CodeNoCreds,
   208  			}
   209  		case 3:
   210  			// Third time: empty password: (the real
   211  			// NewAPIConnection would prompt for it)
   212  			c.Check(p.AccountDetails.User, gc.Equals, "other-user")
   213  			c.Check(p.AccountDetails.Password, gc.Equals, "")
   214  			return s.apiConnection, nil
   215  		default:
   216  			c.Errorf("NewAPIConnection called too many times")
   217  			return nil, errors.Errorf("too many calls")
   218  		}
   219  	}
   220  	stdout, stderr, code := runLogin(c, "other-user\n")
   221  	c.Check(code, gc.Equals, 0)
   222  	c.Check(stdout, gc.Equals, "")
   223  	c.Check(stderr, gc.Matches, `username: Welcome, other-user. (.|\n)+`)
   224  }
   225  
   226  func (s *LoginCommandSuite) TestLoginWithMacaroons(c *gc.C) {
   227  	err := s.store.RemoveAccount("testing")
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	stdout, stderr, code := runLogin(c, "")
   230  	c.Check(stderr, gc.Matches, `
   231  Welcome, user@external. You are now logged into "testing".
   232  
   233  There are no models available(.|\n)*`[1:])
   234  	c.Check(stdout, gc.Equals, ``)
   235  	c.Assert(code, gc.Equals, 0)
   236  	c.Assert(s.apiConnectionParams.AccountDetails, jc.DeepEquals, &jujuclient.AccountDetails{})
   237  }
   238  
   239  func (s *LoginCommandSuite) TestLoginWithMacaroonsNotSupported(c *gc.C) {
   240  	err := s.store.RemoveAccount("testing")
   241  	c.Assert(err, jc.ErrorIsNil)
   242  	*user.NewAPIConnection = func(p juju.NewAPIConnectionParams) (api.Connection, error) {
   243  		if !c.Check(p.AccountDetails, gc.NotNil) {
   244  			return nil, errors.New("no account details")
   245  		}
   246  		if p.AccountDetails.User == "" && p.AccountDetails.Password == "" {
   247  			return nil, &params.Error{Code: params.CodeNoCreds, Message: "barf"}
   248  		}
   249  		c.Check(p.AccountDetails.User, gc.Equals, "new-user")
   250  		return s.apiConnection, nil
   251  	}
   252  	stdout, stderr, code := runLogin(c, "new-user\n")
   253  	c.Check(stdout, gc.Equals, ``)
   254  	c.Check(stderr, gc.Matches, `
   255  username: Welcome, new-user. You are now logged into "testing".
   256  
   257  There are no models available(.|\n)*`[1:])
   258  	c.Assert(code, gc.Equals, 0)
   259  }
   260  
   261  func runLogin(c *gc.C, stdin string, args ...string) (stdout, stderr string, errCode int) {
   262  	c.Logf("in LoginControllerSuite.run")
   263  	var stdoutBuf, stderrBuf bytes.Buffer
   264  	ctxt := &cmd.Context{
   265  		Dir:    c.MkDir(),
   266  		Stdin:  strings.NewReader(stdin),
   267  		Stdout: &stdoutBuf,
   268  		Stderr: &stderrBuf,
   269  	}
   270  	exitCode := cmd.Main(user.NewLoginCommand(), ctxt, args)
   271  	return stdoutBuf.String(), stderrBuf.String(), exitCode
   272  }