github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/admin_test.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"strconv"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/names"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/api"
    21  	apimachiner "github.com/juju/juju/api/machiner"
    22  	apitesting "github.com/juju/juju/api/testing"
    23  	"github.com/juju/juju/apiserver"
    24  	"github.com/juju/juju/apiserver/params"
    25  	jujutesting "github.com/juju/juju/juju/testing"
    26  	"github.com/juju/juju/network"
    27  	"github.com/juju/juju/rpc"
    28  	"github.com/juju/juju/state"
    29  	coretesting "github.com/juju/juju/testing"
    30  	"github.com/juju/juju/testing/factory"
    31  )
    32  
    33  type baseLoginSuite struct {
    34  	jujutesting.JujuConnSuite
    35  	setAdminApi func(*apiserver.Server)
    36  }
    37  
    38  type loginSuite struct {
    39  	baseLoginSuite
    40  }
    41  
    42  var _ = gc.Suite(&loginSuite{
    43  	baseLoginSuite{
    44  		setAdminApi: func(srv *apiserver.Server) {
    45  			apiserver.SetAdminApiVersions(srv, 3)
    46  		},
    47  	},
    48  })
    49  
    50  func (s *baseLoginSuite) SetUpTest(c *gc.C) {
    51  	s.JujuConnSuite.SetUpTest(c)
    52  	loggo.GetLogger("juju.apiserver").SetLogLevel(loggo.TRACE)
    53  }
    54  
    55  func (s *baseLoginSuite) setupServer(c *gc.C) (api.Connection, func()) {
    56  	return s.setupServerForEnvironment(c, s.State.ModelTag())
    57  }
    58  
    59  func (s *baseLoginSuite) setupServerForEnvironment(c *gc.C, modelTag names.ModelTag) (api.Connection, func()) {
    60  	info, cleanup := s.setupServerForEnvironmentWithValidator(c, modelTag, nil)
    61  	st, err := api.Open(info, fastDialOpts)
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	return st, func() {
    64  		st.Close()
    65  		cleanup()
    66  	}
    67  }
    68  
    69  func (s *baseLoginSuite) setupMachineAndServer(c *gc.C) (*api.Info, func()) {
    70  	machine, password := s.Factory.MakeMachineReturningPassword(
    71  		c, &factory.MachineParams{Nonce: "fake_nonce"})
    72  	info, cleanup := s.setupServerWithValidator(c, nil)
    73  	info.Tag = machine.Tag()
    74  	info.Password = password
    75  	info.Nonce = "fake_nonce"
    76  	return info, cleanup
    77  }
    78  
    79  func (s *loginSuite) TestLoginWithInvalidTag(c *gc.C) {
    80  	info := s.APIInfo(c)
    81  	info.Tag = nil
    82  	info.Password = ""
    83  	st := s.openAPIWithoutLogin(c, info)
    84  	defer st.Close()
    85  
    86  	request := &params.LoginRequest{
    87  		AuthTag:     "bar",
    88  		Credentials: "password",
    89  	}
    90  
    91  	var response params.LoginResult
    92  	err := st.APICall("Admin", 3, "", "Login", request, &response)
    93  	c.Assert(err, gc.ErrorMatches, `.*"bar" is not a valid tag.*`)
    94  }
    95  
    96  func (s *loginSuite) TestBadLogin(c *gc.C) {
    97  	// Start our own server so we can control when the first login
    98  	// happens. Otherwise in JujuConnSuite.SetUpTest api.Open is
    99  	// called with user-admin permissions automatically.
   100  	info, cleanup := s.setupServerWithValidator(c, nil)
   101  	defer cleanup()
   102  
   103  	adminUser := s.AdminUserTag(c)
   104  
   105  	for i, t := range []struct {
   106  		tag      names.Tag
   107  		password string
   108  		err      error
   109  		code     string
   110  	}{{
   111  		tag:      adminUser,
   112  		password: "wrong password",
   113  		err: &rpc.RequestError{
   114  			Message: "invalid entity name or password",
   115  			Code:    "unauthorized access",
   116  		},
   117  		code: params.CodeUnauthorized,
   118  	}, {
   119  		tag:      names.NewUserTag("unknown"),
   120  		password: "password",
   121  		err: &rpc.RequestError{
   122  			Message: "invalid entity name or password",
   123  			Code:    "unauthorized access",
   124  		},
   125  		code: params.CodeUnauthorized,
   126  	}} {
   127  		c.Logf("test %d; entity %q; password %q", i, t.tag, t.password)
   128  		func() {
   129  			// Open the API without logging in, so we can perform
   130  			// operations on the connection before calling Login.
   131  			st := s.openAPIWithoutLogin(c, info)
   132  			defer st.Close()
   133  
   134  			_, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0"))
   135  			c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   136  				Message: `unknown object type "Machiner"`,
   137  				Code:    "not implemented",
   138  			})
   139  
   140  			// Since these are user login tests, the nonce is empty.
   141  			err = st.Login(t.tag, t.password, "", nil)
   142  			c.Assert(errors.Cause(err), gc.DeepEquals, t.err)
   143  			c.Assert(params.ErrCode(err), gc.Equals, t.code)
   144  
   145  			_, err = apimachiner.NewState(st).Machine(names.NewMachineTag("0"))
   146  			c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   147  				Message: `unknown object type "Machiner"`,
   148  				Code:    "not implemented",
   149  			})
   150  		}()
   151  	}
   152  }
   153  
   154  func (s *loginSuite) TestLoginAsDeactivatedUser(c *gc.C) {
   155  	info, cleanup := s.setupServerWithValidator(c, nil)
   156  	defer cleanup()
   157  
   158  	st := s.openAPIWithoutLogin(c, info)
   159  	defer st.Close()
   160  	password := "password"
   161  	u := s.Factory.MakeUser(c, &factory.UserParams{Password: password, Disabled: true})
   162  
   163  	_, err := st.Client().Status([]string{})
   164  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   165  		Message: `unknown object type "Client"`,
   166  		Code:    "not implemented",
   167  	})
   168  
   169  	// Since these are user login tests, the nonce is empty.
   170  	err = st.Login(u.Tag(), password, "", nil)
   171  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   172  		Message: "invalid entity name or password",
   173  		Code:    "unauthorized access",
   174  	})
   175  
   176  	_, err = st.Client().Status([]string{})
   177  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   178  		Message: `unknown object type "Client"`,
   179  		Code:    "not implemented",
   180  	})
   181  }
   182  
   183  func (s *baseLoginSuite) runLoginSetsLogIdentifier(c *gc.C) {
   184  	info, cleanup := s.setupServerWithValidator(c, nil)
   185  	defer cleanup()
   186  
   187  	machine, password := s.Factory.MakeMachineReturningPassword(
   188  		c, &factory.MachineParams{Nonce: "fake_nonce"})
   189  
   190  	info.Tag = machine.Tag()
   191  	info.Password = password
   192  	info.Nonce = "fake_nonce"
   193  
   194  	apiConn, err := api.Open(info, fastDialOpts)
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	defer apiConn.Close()
   197  
   198  	apiMachine, err := apimachiner.NewState(apiConn).Machine(machine.MachineTag())
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	c.Assert(apiMachine.Tag(), gc.Equals, machine.Tag())
   201  }
   202  
   203  func (s *loginSuite) TestLoginAddrs(c *gc.C) {
   204  	info, cleanup := s.setupMachineAndServer(c)
   205  	defer cleanup()
   206  
   207  	err := s.State.SetAPIHostPorts(nil)
   208  	c.Assert(err, jc.ErrorIsNil)
   209  
   210  	// Initially just the address we connect with is returned,
   211  	// despite there being no APIHostPorts in state.
   212  	connectedAddr, hostPorts := s.loginHostPorts(c, info)
   213  	connectedAddrHost, connectedAddrPortString, err := net.SplitHostPort(connectedAddr)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	connectedAddrPort, err := strconv.Atoi(connectedAddrPortString)
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	connectedAddrHostPorts := [][]network.HostPort{
   218  		network.NewHostPorts(connectedAddrPort, connectedAddrHost),
   219  	}
   220  	c.Assert(hostPorts, gc.DeepEquals, connectedAddrHostPorts)
   221  
   222  	// After storing APIHostPorts in state, Login should store
   223  	// all of them and the address we connected with.
   224  	server1Addresses := []network.Address{{
   225  		Value: "server-1",
   226  		Type:  network.HostName,
   227  		Scope: network.ScopePublic,
   228  	}, {
   229  		Value: "10.0.0.1",
   230  		Type:  network.IPv4Address,
   231  		Scope: network.ScopeCloudLocal,
   232  	}}
   233  	server2Addresses := []network.Address{{
   234  		Value: "::1",
   235  		Type:  network.IPv6Address,
   236  		Scope: network.ScopeMachineLocal,
   237  	}}
   238  	stateAPIHostPorts := [][]network.HostPort{
   239  		network.AddressesWithPort(server1Addresses, 123),
   240  		network.AddressesWithPort(server2Addresses, 456),
   241  	}
   242  	err = s.State.SetAPIHostPorts(stateAPIHostPorts)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	_, hostPorts = s.loginHostPorts(c, info)
   245  	// Now that we connected, we add the other stateAPIHostPorts. However,
   246  	// the one we connected to comes first.
   247  	stateAPIHostPorts = append(connectedAddrHostPorts, stateAPIHostPorts...)
   248  	c.Assert(hostPorts, gc.DeepEquals, stateAPIHostPorts)
   249  }
   250  
   251  func (s *baseLoginSuite) loginHostPorts(c *gc.C, info *api.Info) (connectedAddr string, hostPorts [][]network.HostPort) {
   252  	st, err := api.Open(info, fastDialOpts)
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	defer st.Close()
   255  	return st.Addr(), st.APIHostPorts()
   256  }
   257  
   258  func startNLogins(c *gc.C, n int, info *api.Info) (chan error, *sync.WaitGroup) {
   259  	errResults := make(chan error, 100)
   260  	var doneWG sync.WaitGroup
   261  	var startedWG sync.WaitGroup
   262  	c.Logf("starting %d concurrent logins to %v", n, info.Addrs)
   263  	for i := 0; i < n; i++ {
   264  		i := i
   265  		c.Logf("starting login request %d", i)
   266  		startedWG.Add(1)
   267  		doneWG.Add(1)
   268  		go func() {
   269  			c.Logf("started login %d", i)
   270  			startedWG.Done()
   271  			st, err := api.Open(info, fastDialOpts)
   272  			errResults <- err
   273  			if err == nil {
   274  				st.Close()
   275  			}
   276  			doneWG.Done()
   277  			c.Logf("finished login %d: %v", i, err)
   278  		}()
   279  	}
   280  	startedWG.Wait()
   281  	return errResults, &doneWG
   282  }
   283  
   284  func (s *loginSuite) TestDelayLogins(c *gc.C) {
   285  	info, cleanup := s.setupMachineAndServer(c)
   286  	defer cleanup()
   287  	delayChan, cleanup := apiserver.DelayLogins()
   288  	defer cleanup()
   289  
   290  	// numConcurrentLogins is how many logins will fire off simultaneously.
   291  	// It doesn't really matter, as long as it is less than LoginRateLimit
   292  	const numConcurrentLogins = 5
   293  	c.Assert(numConcurrentLogins, jc.LessThan, apiserver.LoginRateLimit)
   294  	// Trigger a bunch of login requests
   295  	errResults, wg := startNLogins(c, numConcurrentLogins, info)
   296  	select {
   297  	case err := <-errResults:
   298  		c.Fatalf("we should not have gotten any logins yet: %v", err)
   299  	case <-time.After(coretesting.ShortWait):
   300  	}
   301  	// Allow one login to proceed
   302  	c.Logf("letting one login through")
   303  	select {
   304  	case delayChan <- struct{}{}:
   305  	default:
   306  		c.Fatalf("we should have been able to unblock a login")
   307  	}
   308  	select {
   309  	case err := <-errResults:
   310  		c.Check(err, jc.ErrorIsNil)
   311  	case <-time.After(coretesting.LongWait):
   312  		c.Fatalf("timed out while waiting for Login to finish")
   313  	}
   314  	c.Logf("checking no other logins succeeded")
   315  	// It should have only let 1 login through
   316  	select {
   317  	case err := <-errResults:
   318  		c.Fatalf("we should not have gotten more logins: %v", err)
   319  	case <-time.After(coretesting.ShortWait):
   320  	}
   321  	// Now allow the rest of the logins to proceed
   322  	c.Logf("letting %d logins through", numConcurrentLogins-1)
   323  	for i := 0; i < numConcurrentLogins-1; i++ {
   324  		delayChan <- struct{}{}
   325  	}
   326  	c.Logf("waiting for Logins to finish")
   327  	wg.Wait()
   328  	close(errResults)
   329  	successCount := 0
   330  	for err := range errResults {
   331  		c.Check(err, jc.ErrorIsNil)
   332  		if err == nil {
   333  			successCount += 1
   334  		}
   335  	}
   336  	// All the logins should succeed, they were just delayed after
   337  	// connecting.
   338  	c.Check(successCount, gc.Equals, numConcurrentLogins-1)
   339  	c.Logf("done")
   340  }
   341  
   342  func (s *loginSuite) TestLoginRateLimited(c *gc.C) {
   343  	info, cleanup := s.setupMachineAndServer(c)
   344  	defer cleanup()
   345  	delayChan, cleanup := apiserver.DelayLogins()
   346  	defer cleanup()
   347  
   348  	// Start enough concurrent Login requests so that we max out our
   349  	// LoginRateLimit. Do one extra so we know we are in overload
   350  	errResults, wg := startNLogins(c, apiserver.LoginRateLimit+1, info)
   351  	select {
   352  	case err := <-errResults:
   353  		c.Check(err, jc.Satisfies, params.IsCodeTryAgain)
   354  	case <-time.After(coretesting.LongWait):
   355  		c.Fatalf("timed out waiting for login to get rejected.")
   356  	}
   357  
   358  	// Let one request through, we should see that it succeeds without
   359  	// error, and then be able to start a new request, but it will block
   360  	delayChan <- struct{}{}
   361  	select {
   362  	case err := <-errResults:
   363  		c.Check(err, jc.ErrorIsNil)
   364  	case <-time.After(coretesting.LongWait):
   365  		c.Fatalf("timed out expecting one login to succeed")
   366  	}
   367  	chOne := make(chan error, 1)
   368  	wg.Add(1)
   369  	go func() {
   370  		st, err := api.Open(info, fastDialOpts)
   371  		chOne <- err
   372  		if err == nil {
   373  			st.Close()
   374  		}
   375  		wg.Done()
   376  	}()
   377  	select {
   378  	case err := <-chOne:
   379  		c.Fatalf("the open request should not have completed: %v", err)
   380  	case <-time.After(coretesting.ShortWait):
   381  	}
   382  	// Let all the logins finish. We started with LoginRateLimit, let one
   383  	// proceed, but we issued another one, so there should be
   384  	// LoginRateLimit logins pending.
   385  	for i := 0; i < apiserver.LoginRateLimit; i++ {
   386  		delayChan <- struct{}{}
   387  	}
   388  	wg.Wait()
   389  	close(errResults)
   390  	for err := range errResults {
   391  		c.Check(err, jc.ErrorIsNil)
   392  	}
   393  }
   394  
   395  func (s *loginSuite) TestUsersLoginWhileRateLimited(c *gc.C) {
   396  	info, cleanup := s.setupMachineAndServer(c)
   397  	defer cleanup()
   398  	delayChan, cleanup := apiserver.DelayLogins()
   399  	defer cleanup()
   400  
   401  	// Start enough concurrent Login requests so that we max out our
   402  	// LoginRateLimit. Do one extra so we know we are in overload
   403  	machineResults, machineWG := startNLogins(c, apiserver.LoginRateLimit+1, info)
   404  	select {
   405  	case err := <-machineResults:
   406  		c.Check(err, jc.Satisfies, params.IsCodeTryAgain)
   407  	case <-time.After(coretesting.LongWait):
   408  		c.Fatalf("timed out waiting for login to get rejected.")
   409  	}
   410  
   411  	userInfo := *info
   412  	userInfo.Tag = s.AdminUserTag(c)
   413  	userInfo.Password = "dummy-secret"
   414  	userResults, userWG := startNLogins(c, apiserver.LoginRateLimit+1, &userInfo)
   415  	// all of them should have started, and none of them in TryAgain state
   416  	select {
   417  	case err := <-userResults:
   418  		c.Fatalf("we should not have gotten any logins yet: %v", err)
   419  	case <-time.After(coretesting.ShortWait):
   420  	}
   421  	totalLogins := apiserver.LoginRateLimit*2 + 1
   422  	for i := 0; i < totalLogins; i++ {
   423  		delayChan <- struct{}{}
   424  	}
   425  	machineWG.Wait()
   426  	close(machineResults)
   427  	userWG.Wait()
   428  	close(userResults)
   429  	machineCount := 0
   430  	for err := range machineResults {
   431  		machineCount += 1
   432  		c.Check(err, jc.ErrorIsNil)
   433  	}
   434  	c.Check(machineCount, gc.Equals, apiserver.LoginRateLimit)
   435  	userCount := 0
   436  	for err := range userResults {
   437  		userCount += 1
   438  		c.Check(err, jc.ErrorIsNil)
   439  	}
   440  	c.Check(userCount, gc.Equals, apiserver.LoginRateLimit+1)
   441  }
   442  
   443  func (s *loginSuite) TestUsersAreNotRateLimited(c *gc.C) {
   444  	info, cleanup := s.setupServerWithValidator(c, nil)
   445  	info.Tag = s.AdminUserTag(c)
   446  	info.Password = "dummy-secret"
   447  	defer cleanup()
   448  	delayChan, cleanup := apiserver.DelayLogins()
   449  	defer cleanup()
   450  	// We can login more than LoginRateLimit users
   451  	nLogins := apiserver.LoginRateLimit * 2
   452  	errResults, wg := startNLogins(c, nLogins, info)
   453  	select {
   454  	case err := <-errResults:
   455  		c.Fatalf("we should not have gotten any logins yet: %v", err)
   456  	case <-time.After(coretesting.ShortWait):
   457  	}
   458  	c.Logf("letting %d logins complete", nLogins)
   459  	for i := 0; i < nLogins; i++ {
   460  		delayChan <- struct{}{}
   461  	}
   462  	c.Logf("waiting for original requests to finish")
   463  	wg.Wait()
   464  	close(errResults)
   465  	for err := range errResults {
   466  		c.Check(err, jc.ErrorIsNil)
   467  	}
   468  }
   469  
   470  func (s *loginSuite) TestNonEnvironUserLoginFails(c *gc.C) {
   471  	info, cleanup := s.setupServerWithValidator(c, nil)
   472  	defer cleanup()
   473  	user := s.Factory.MakeUser(c, &factory.UserParams{Password: "dummy-password", NoModelUser: true})
   474  	info.Password = "dummy-password"
   475  	info.Tag = user.UserTag()
   476  	_, err := api.Open(info, fastDialOpts)
   477  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   478  		Message: "invalid entity name or password",
   479  		Code:    "unauthorized access",
   480  	})
   481  }
   482  
   483  func (s *loginSuite) TestLoginValidationSuccess(c *gc.C) {
   484  	validator := func(params.LoginRequest) error {
   485  		return nil
   486  	}
   487  	checker := func(c *gc.C, loginErr error, st api.Connection) {
   488  		c.Assert(loginErr, gc.IsNil)
   489  
   490  		// Ensure an API call that would be restricted during
   491  		// upgrades works after a normal login.
   492  		err := st.APICall("Client", 1, "", "DestroyModel", nil, nil)
   493  		c.Assert(err, jc.ErrorIsNil)
   494  	}
   495  	s.checkLoginWithValidator(c, validator, checker)
   496  }
   497  
   498  func (s *loginSuite) TestLoginValidationFail(c *gc.C) {
   499  	validator := func(params.LoginRequest) error {
   500  		return errors.New("Login not allowed")
   501  	}
   502  	checker := func(c *gc.C, loginErr error, _ api.Connection) {
   503  		// error is wrapped in API server
   504  		c.Assert(loginErr, gc.ErrorMatches, "Login not allowed")
   505  	}
   506  	s.checkLoginWithValidator(c, validator, checker)
   507  }
   508  
   509  func (s *loginSuite) TestLoginValidationDuringUpgrade(c *gc.C) {
   510  	validator := func(params.LoginRequest) error {
   511  		return params.UpgradeInProgressError
   512  	}
   513  	checker := func(c *gc.C, loginErr error, st api.Connection) {
   514  		c.Assert(loginErr, gc.IsNil)
   515  
   516  		var statusResult params.FullStatus
   517  		err := st.APICall("Client", 1, "", "FullStatus", params.StatusParams{}, &statusResult)
   518  		c.Assert(err, jc.ErrorIsNil)
   519  
   520  		err = st.APICall("Client", 1, "", "DestroyModel", nil, nil)
   521  		c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{Message: params.CodeUpgradeInProgress, Code: params.CodeUpgradeInProgress})
   522  	}
   523  	s.checkLoginWithValidator(c, validator, checker)
   524  }
   525  
   526  func (s *loginSuite) TestFailedLoginDuringMaintenance(c *gc.C) {
   527  	validator := func(params.LoginRequest) error {
   528  		return errors.New("something")
   529  	}
   530  	info, cleanup := s.setupServerWithValidator(c, validator)
   531  	defer cleanup()
   532  
   533  	checkLogin := func(tag names.Tag) {
   534  		st := s.openAPIWithoutLogin(c, info)
   535  		defer st.Close()
   536  		err := st.Login(tag, "dummy-secret", "nonce", nil)
   537  		c.Assert(err, gc.ErrorMatches, "something")
   538  	}
   539  	checkLogin(names.NewUserTag("definitelywontexist"))
   540  	checkLogin(names.NewMachineTag("99999"))
   541  }
   542  
   543  type validationChecker func(c *gc.C, err error, st api.Connection)
   544  
   545  func (s *baseLoginSuite) checkLoginWithValidator(c *gc.C, validator apiserver.LoginValidator, checker validationChecker) {
   546  	info, cleanup := s.setupServerWithValidator(c, validator)
   547  	defer cleanup()
   548  
   549  	st := s.openAPIWithoutLogin(c, info)
   550  	defer st.Close()
   551  
   552  	// Ensure not already logged in.
   553  	_, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0"))
   554  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   555  		Message: `unknown object type "Machiner"`,
   556  		Code:    "not implemented",
   557  	})
   558  
   559  	adminUser := s.AdminUserTag(c)
   560  	// Since these are user login tests, the nonce is empty.
   561  	err = st.Login(adminUser, "dummy-secret", "", nil)
   562  
   563  	checker(c, err, st)
   564  }
   565  
   566  func (s *baseLoginSuite) setupServerWithValidator(c *gc.C, validator apiserver.LoginValidator) (*api.Info, func()) {
   567  	env, err := s.State.Model()
   568  	c.Assert(err, jc.ErrorIsNil)
   569  	return s.setupServerForEnvironmentWithValidator(c, env.ModelTag(), validator)
   570  }
   571  
   572  func (s *baseLoginSuite) setupServerForEnvironmentWithValidator(c *gc.C, modelTag names.ModelTag, validator apiserver.LoginValidator) (*api.Info, func()) {
   573  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   574  	c.Assert(err, jc.ErrorIsNil)
   575  	srv, err := apiserver.NewServer(
   576  		s.State,
   577  		listener,
   578  		apiserver.ServerConfig{
   579  			Cert:      []byte(coretesting.ServerCert),
   580  			Key:       []byte(coretesting.ServerKey),
   581  			Validator: validator,
   582  			Tag:       names.NewMachineTag("0"),
   583  			LogDir:    c.MkDir(),
   584  		},
   585  	)
   586  	c.Assert(err, jc.ErrorIsNil)
   587  	c.Assert(s.setAdminApi, gc.NotNil)
   588  	s.setAdminApi(srv)
   589  	info := &api.Info{
   590  		Tag:      nil,
   591  		Password: "",
   592  		ModelTag: modelTag,
   593  		Addrs:    []string{srv.Addr().String()},
   594  		CACert:   coretesting.CACert,
   595  	}
   596  	return info, func() {
   597  		err := srv.Stop()
   598  		c.Assert(err, jc.ErrorIsNil)
   599  	}
   600  }
   601  
   602  func (s *baseLoginSuite) openAPIWithoutLogin(c *gc.C, info *api.Info) api.Connection {
   603  	info.Tag = nil
   604  	info.Password = ""
   605  	info.SkipLogin = true
   606  	st, err := api.Open(info, fastDialOpts)
   607  	c.Assert(err, jc.ErrorIsNil)
   608  	return st
   609  }
   610  
   611  func (s *loginSuite) TestControllerModel(c *gc.C) {
   612  	info, cleanup := s.setupServerWithValidator(c, nil)
   613  	defer cleanup()
   614  
   615  	c.Assert(info.ModelTag, gc.Equals, s.State.ModelTag())
   616  	st := s.openAPIWithoutLogin(c, info)
   617  	defer st.Close()
   618  
   619  	adminUser := s.AdminUserTag(c)
   620  	err := st.Login(adminUser, "dummy-secret", "", nil)
   621  	c.Assert(err, jc.ErrorIsNil)
   622  
   623  	s.assertRemoteEnvironment(c, st, s.State.ModelTag())
   624  }
   625  
   626  func (s *loginSuite) TestControllerModelBadCreds(c *gc.C) {
   627  	info, cleanup := s.setupServerWithValidator(c, nil)
   628  	defer cleanup()
   629  
   630  	c.Assert(info.ModelTag, gc.Equals, s.State.ModelTag())
   631  	st := s.openAPIWithoutLogin(c, info)
   632  	defer st.Close()
   633  
   634  	adminUser := s.AdminUserTag(c)
   635  	err := st.Login(adminUser, "bad-password", "", nil)
   636  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   637  		Message: `invalid entity name or password`,
   638  		Code:    "unauthorized access",
   639  	})
   640  }
   641  
   642  func (s *loginSuite) TestNonExistentEnvironment(c *gc.C) {
   643  	info, cleanup := s.setupServerWithValidator(c, nil)
   644  	defer cleanup()
   645  
   646  	uuid, err := utils.NewUUID()
   647  	c.Assert(err, jc.ErrorIsNil)
   648  	info.ModelTag = names.NewModelTag(uuid.String())
   649  	st := s.openAPIWithoutLogin(c, info)
   650  	defer st.Close()
   651  
   652  	adminUser := s.AdminUserTag(c)
   653  	err = st.Login(adminUser, "dummy-secret", "", nil)
   654  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   655  		Message: fmt.Sprintf("unknown model: %q", uuid),
   656  		Code:    "not found",
   657  	})
   658  }
   659  
   660  func (s *loginSuite) TestInvalidEnvironment(c *gc.C) {
   661  	info, cleanup := s.setupServerWithValidator(c, nil)
   662  	defer cleanup()
   663  
   664  	info.ModelTag = names.NewModelTag("rubbish")
   665  	st := s.openAPIWithoutLogin(c, info)
   666  	defer st.Close()
   667  
   668  	adminUser := s.AdminUserTag(c)
   669  	err := st.Login(adminUser, "dummy-secret", "", nil)
   670  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   671  		Message: `unknown model: "rubbish"`,
   672  		Code:    "not found",
   673  	})
   674  }
   675  
   676  func (s *loginSuite) TestOtherEnvironment(c *gc.C) {
   677  	info, cleanup := s.setupServerWithValidator(c, nil)
   678  	defer cleanup()
   679  
   680  	envOwner := s.Factory.MakeUser(c, nil)
   681  	envState := s.Factory.MakeModel(c, &factory.ModelParams{
   682  		Owner: envOwner.UserTag(),
   683  	})
   684  	defer envState.Close()
   685  	info.ModelTag = envState.ModelTag()
   686  	st := s.openAPIWithoutLogin(c, info)
   687  	defer st.Close()
   688  
   689  	err := st.Login(envOwner.UserTag(), "password", "", nil)
   690  	c.Assert(err, jc.ErrorIsNil)
   691  	s.assertRemoteEnvironment(c, st, envState.ModelTag())
   692  }
   693  
   694  func (s *loginSuite) TestMachineLoginOtherEnvironment(c *gc.C) {
   695  	// User credentials are checked against a global user list.
   696  	// Machine credentials are checked against environment specific
   697  	// machines, so this makes sure that the credential checking is
   698  	// using the correct state connection.
   699  	info, cleanup := s.setupServerWithValidator(c, nil)
   700  	defer cleanup()
   701  
   702  	envOwner := s.Factory.MakeUser(c, nil)
   703  	envState := s.Factory.MakeModel(c, &factory.ModelParams{
   704  		Owner: envOwner.UserTag(),
   705  		ConfigAttrs: map[string]interface{}{
   706  			"controller": false,
   707  		},
   708  	})
   709  	defer envState.Close()
   710  
   711  	f2 := factory.NewFactory(envState)
   712  	machine, password := f2.MakeMachineReturningPassword(c, &factory.MachineParams{
   713  		Nonce: "nonce",
   714  	})
   715  
   716  	info.ModelTag = envState.ModelTag()
   717  	st := s.openAPIWithoutLogin(c, info)
   718  	defer st.Close()
   719  
   720  	err := st.Login(machine.Tag(), password, "nonce", nil)
   721  	c.Assert(err, jc.ErrorIsNil)
   722  }
   723  
   724  func (s *loginSuite) TestOtherEnvironmentFromController(c *gc.C) {
   725  	info, cleanup := s.setupServerWithValidator(c, nil)
   726  	defer cleanup()
   727  
   728  	machine, password := s.Factory.MakeMachineReturningPassword(c, &factory.MachineParams{
   729  		Jobs: []state.MachineJob{state.JobManageModel},
   730  	})
   731  
   732  	envState := s.Factory.MakeModel(c, nil)
   733  	defer envState.Close()
   734  	info.ModelTag = envState.ModelTag()
   735  	st := s.openAPIWithoutLogin(c, info)
   736  	defer st.Close()
   737  
   738  	err := st.Login(machine.Tag(), password, "nonce", nil)
   739  	c.Assert(err, jc.ErrorIsNil)
   740  }
   741  
   742  func (s *loginSuite) TestOtherEnvironmentWhenNotController(c *gc.C) {
   743  	info, cleanup := s.setupServerWithValidator(c, nil)
   744  	defer cleanup()
   745  
   746  	machine, password := s.Factory.MakeMachineReturningPassword(c, nil)
   747  
   748  	envState := s.Factory.MakeModel(c, nil)
   749  	defer envState.Close()
   750  	info.ModelTag = envState.ModelTag()
   751  	st := s.openAPIWithoutLogin(c, info)
   752  	defer st.Close()
   753  
   754  	err := st.Login(machine.Tag(), password, "nonce", nil)
   755  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   756  		Message: "invalid entity name or password",
   757  		Code:    "unauthorized access",
   758  	})
   759  }
   760  
   761  func (s *loginSuite) assertRemoteEnvironment(c *gc.C, st api.Connection, expected names.ModelTag) {
   762  	// Look at what the api thinks it has.
   763  	tag, err := st.ModelTag()
   764  	c.Assert(err, jc.ErrorIsNil)
   765  	c.Assert(tag, gc.Equals, expected)
   766  	// Look at what the api Client thinks it has.
   767  	client := st.Client()
   768  
   769  	// ModelUUID looks at the env tag on the api state connection.
   770  	c.Assert(client.ModelUUID(), gc.Equals, expected.Id())
   771  
   772  	// ModelInfo calls a remote method that looks up the environment.
   773  	info, err := client.ModelInfo()
   774  	c.Assert(err, jc.ErrorIsNil)
   775  	c.Assert(info.UUID, gc.Equals, expected.Id())
   776  }
   777  
   778  func (s *loginSuite) TestLoginUpdatesLastLoginAndConnection(c *gc.C) {
   779  	_, cleanup := s.setupServerWithValidator(c, nil)
   780  	defer cleanup()
   781  
   782  	// Since the login and connection times truncate time to the second,
   783  	// we need to make sure our start time is just before now.
   784  	startTime := time.Now().Add(-time.Second)
   785  
   786  	password := "shhh..."
   787  	user := s.Factory.MakeUser(c, &factory.UserParams{
   788  		Password: password,
   789  	})
   790  
   791  	info := s.APIInfo(c)
   792  	info.Tag = user.Tag()
   793  	info.Password = password
   794  	apiState, err := api.Open(info, api.DialOpts{})
   795  	c.Assert(err, jc.ErrorIsNil)
   796  	defer apiState.Close()
   797  
   798  	// The user now has last login updated.
   799  	err = user.Refresh()
   800  	c.Assert(err, jc.ErrorIsNil)
   801  	lastLogin, err := user.LastLogin()
   802  	c.Assert(err, jc.ErrorIsNil)
   803  	c.Assert(lastLogin, gc.NotNil)
   804  	c.Assert(lastLogin.After(startTime), jc.IsTrue)
   805  
   806  	// The env user is also updated.
   807  	modelUser, err := s.State.ModelUser(user.UserTag())
   808  	c.Assert(err, jc.ErrorIsNil)
   809  	when, err := modelUser.LastConnection()
   810  	c.Assert(err, jc.ErrorIsNil)
   811  	c.Assert(when, gc.NotNil)
   812  	c.Assert(when.After(startTime), jc.IsTrue)
   813  }
   814  
   815  var _ = gc.Suite(&macaroonLoginSuite{})
   816  
   817  type macaroonLoginSuite struct {
   818  	apitesting.MacaroonSuite
   819  }
   820  
   821  func (s *macaroonLoginSuite) TestLoginToController(c *gc.C) {
   822  	// Note that currently we cannot use macaroon auth
   823  	// to log into the controller rather than an environment
   824  	// because there's no place to store the fact that
   825  	// a given external user is allowed access to the controller.
   826  	s.DischargerLogin = func() string {
   827  		return "test@somewhere"
   828  	}
   829  	info := s.APIInfo(c)
   830  
   831  	// Zero the environment tag so that we log into the controller
   832  	// not the environment.
   833  	info.ModelTag = names.ModelTag{}
   834  
   835  	client, err := api.Open(info, api.DialOpts{})
   836  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   837  		Message: "invalid entity name or password",
   838  		Code:    "unauthorized access",
   839  	})
   840  	c.Assert(client, gc.Equals, nil)
   841  }
   842  
   843  func (s *macaroonLoginSuite) TestLoginToEnvironmentSuccess(c *gc.C) {
   844  	s.AddModelUser(c, "test@somewhere")
   845  	s.DischargerLogin = func() string {
   846  		return "test@somewhere"
   847  	}
   848  	client, err := api.Open(s.APIInfo(c), api.DialOpts{})
   849  	c.Assert(err, jc.ErrorIsNil)
   850  	defer client.Close()
   851  
   852  	// The auth tag has been correctly returned by the server.
   853  	c.Assert(client.AuthTag(), gc.Equals, names.NewUserTag("test@somewhere"))
   854  }
   855  
   856  func (s *macaroonLoginSuite) TestFailedToObtainDischargeLogin(c *gc.C) {
   857  	s.DischargerLogin = func() string {
   858  		return ""
   859  	}
   860  	client, err := api.Open(s.APIInfo(c), api.DialOpts{})
   861  	c.Assert(err, gc.ErrorMatches, `cannot get discharge from "https://.*": third party refused discharge: cannot discharge: login denied by discharger`)
   862  	c.Assert(client, gc.Equals, nil)
   863  }
   864  
   865  func (s *macaroonLoginSuite) TestUnknownUserLogin(c *gc.C) {
   866  	s.DischargerLogin = func() string {
   867  		return "testUnknown@somewhere"
   868  	}
   869  	client, err := api.Open(s.APIInfo(c), api.DialOpts{})
   870  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   871  		Message: "invalid entity name or password",
   872  		Code:    "unauthorized access",
   873  	})
   874  	c.Assert(client, gc.Equals, nil)
   875  }