github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/authentication/user_test.go (about)

     1  // Copyright 2014 Canonical Ltd. All rights reserved.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package authentication_test
     5  
     6  import (
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/names.v2"
    18  	"gopkg.in/macaroon-bakery.v2-unstable/bakery"
    19  	"gopkg.in/macaroon-bakery.v2-unstable/bakery/checkers"
    20  	"gopkg.in/macaroon-bakery.v2-unstable/bakerytest"
    21  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    22  	"gopkg.in/macaroon.v2-unstable"
    23  
    24  	"github.com/juju/juju/apiserver/authentication"
    25  	"github.com/juju/juju/apiserver/common"
    26  	"github.com/juju/juju/apiserver/params"
    27  	jujutesting "github.com/juju/juju/juju/testing"
    28  	"github.com/juju/juju/state"
    29  	"github.com/juju/juju/testing/factory"
    30  )
    31  
    32  var logger = loggo.GetLogger("juju.apiserver.authentication")
    33  
    34  type userAuthenticatorSuite struct {
    35  	jujutesting.JujuConnSuite
    36  }
    37  
    38  type entityFinder struct {
    39  	entity state.Entity
    40  }
    41  
    42  func (f entityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
    43  	return f.entity, nil
    44  }
    45  
    46  var _ = gc.Suite(&userAuthenticatorSuite{})
    47  
    48  func (s *userAuthenticatorSuite) TestMachineLoginFails(c *gc.C) {
    49  	// add machine for testing machine agent authentication
    50  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	nonce, err := utils.RandomPassword()
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	err = machine.SetProvisioned("foo", "", nonce, nil)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  	password, err := utils.RandomPassword()
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	err = machine.SetPassword(password)
    59  	c.Assert(err, jc.ErrorIsNil)
    60  	machinePassword := password
    61  
    62  	// attempt machine login
    63  	authenticator := &authentication.UserAuthenticator{}
    64  	_, err = authenticator.Authenticate(nil, machine.Tag(), params.LoginRequest{
    65  		Credentials: machinePassword,
    66  		Nonce:       nonce,
    67  	})
    68  	c.Assert(err, gc.ErrorMatches, "invalid request")
    69  }
    70  
    71  func (s *userAuthenticatorSuite) TestUnitLoginFails(c *gc.C) {
    72  	// add a unit for testing unit agent authentication
    73  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    74  	unit, err := wordpress.AddUnit(state.AddUnitParams{})
    75  	c.Assert(err, jc.ErrorIsNil)
    76  	password, err := utils.RandomPassword()
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	err = unit.SetPassword(password)
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	unitPassword := password
    81  
    82  	// Attempt unit login
    83  	authenticator := &authentication.UserAuthenticator{}
    84  	_, err = authenticator.Authenticate(nil, unit.Tag(), params.LoginRequest{
    85  		Credentials: unitPassword,
    86  		Nonce:       "",
    87  	})
    88  	c.Assert(err, gc.ErrorMatches, "invalid request")
    89  }
    90  
    91  func (s *userAuthenticatorSuite) TestValidUserLogin(c *gc.C) {
    92  	user := s.Factory.MakeUser(c, &factory.UserParams{
    93  		Name:        "bobbrown",
    94  		DisplayName: "Bob Brown",
    95  		Password:    "password",
    96  	})
    97  
    98  	// User login
    99  	authenticator := &authentication.UserAuthenticator{}
   100  	_, err := authenticator.Authenticate(s.State, user.Tag(), params.LoginRequest{
   101  		Credentials: "password",
   102  		Nonce:       "",
   103  	})
   104  	c.Assert(err, jc.ErrorIsNil)
   105  }
   106  
   107  func (s *userAuthenticatorSuite) TestUserLoginWrongPassword(c *gc.C) {
   108  	user := s.Factory.MakeUser(c, &factory.UserParams{
   109  		Name:        "bobbrown",
   110  		DisplayName: "Bob Brown",
   111  		Password:    "password",
   112  	})
   113  
   114  	// User login
   115  	authenticator := &authentication.UserAuthenticator{}
   116  	_, err := authenticator.Authenticate(s.State, user.Tag(), params.LoginRequest{
   117  		Credentials: "wrongpassword",
   118  		Nonce:       "",
   119  	})
   120  	c.Assert(err, gc.ErrorMatches, "invalid entity name or password")
   121  
   122  }
   123  
   124  func (s *userAuthenticatorSuite) TestInvalidRelationLogin(c *gc.C) {
   125  
   126  	// add relation
   127  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   128  	wordpressEP, err := wordpress.Endpoint("db")
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   131  	mysqlEP, err := mysql.Endpoint("server")
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   134  	c.Assert(err, jc.ErrorIsNil)
   135  
   136  	// Attempt relation login
   137  	authenticator := &authentication.UserAuthenticator{}
   138  	_, err = authenticator.Authenticate(nil, relation.Tag(), params.LoginRequest{
   139  		Credentials: "dummy-secret",
   140  		Nonce:       "",
   141  	})
   142  	c.Assert(err, gc.ErrorMatches, "invalid request")
   143  }
   144  
   145  func (s *userAuthenticatorSuite) TestValidMacaroonUserLogin(c *gc.C) {
   146  	user := s.Factory.MakeUser(c, &factory.UserParams{
   147  		Name: "bobbrown",
   148  	})
   149  	macaroons := []macaroon.Slice{{&macaroon.Macaroon{}}}
   150  	service := mockBakeryService{}
   151  
   152  	// User login
   153  	authenticator := &authentication.UserAuthenticator{Service: &service}
   154  	_, err := authenticator.Authenticate(s.State, user.Tag(), params.LoginRequest{
   155  		Credentials: "",
   156  		Nonce:       "",
   157  		Macaroons:   macaroons,
   158  	})
   159  	c.Assert(err, jc.ErrorIsNil)
   160  
   161  	service.CheckCallNames(c, "CheckAny")
   162  	call := service.Calls()[0]
   163  	c.Assert(call.Args, gc.HasLen, 3)
   164  	c.Assert(call.Args[0], jc.DeepEquals, macaroons)
   165  	c.Assert(call.Args[1], jc.DeepEquals, map[string]string{"username": "bobbrown"})
   166  	// no check for checker function, can't compare functions
   167  }
   168  
   169  func (s *userAuthenticatorSuite) TestCreateLocalLoginMacaroon(c *gc.C) {
   170  	service := mockBakeryService{}
   171  	clock := testclock.NewClock(time.Time{})
   172  	_, err := authentication.CreateLocalLoginMacaroon(
   173  		names.NewUserTag("bobbrown"), &service, clock,
   174  	)
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	service.CheckCallNames(c, "NewMacaroon")
   177  	service.CheckCall(c, 0, "NewMacaroon", []checkers.Caveat{
   178  		{Condition: "is-authenticated-user bobbrown"},
   179  		{Condition: "time-before 0001-01-01T00:02:00Z"},
   180  	})
   181  }
   182  
   183  func (s *userAuthenticatorSuite) TestAuthenticateLocalLoginMacaroon(c *gc.C) {
   184  	service := mockBakeryService{}
   185  	clock := testclock.NewClock(time.Time{})
   186  	authenticator := &authentication.UserAuthenticator{
   187  		Service:                   &service,
   188  		Clock:                     clock,
   189  		LocalUserIdentityLocation: "https://testing.invalid:1234/auth",
   190  	}
   191  
   192  	service.SetErrors(&bakery.VerificationError{})
   193  	_, err := authenticator.Authenticate(
   194  		authentication.EntityFinder(nil),
   195  		names.NewUserTag("bobbrown"),
   196  		params.LoginRequest{},
   197  	)
   198  	c.Assert(err, gc.FitsTypeOf, &common.DischargeRequiredError{})
   199  
   200  	service.CheckCallNames(c, "CheckAny", "ExpireStorageAfter", "NewMacaroon")
   201  	calls := service.Calls()
   202  	c.Assert(calls[1].Args, jc.DeepEquals, []interface{}{24 * time.Hour})
   203  	c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{
   204  		[]checkers.Caveat{
   205  			checkers.NeedDeclaredCaveat(
   206  				checkers.Caveat{
   207  					Location:  "https://testing.invalid:1234/auth",
   208  					Condition: "is-authenticated-user bobbrown",
   209  				},
   210  				"username",
   211  			),
   212  			{Condition: "time-before 0001-01-02T00:00:00Z"},
   213  		},
   214  	})
   215  }
   216  
   217  type mockBakeryService struct {
   218  	testing.Stub
   219  }
   220  
   221  func (s *mockBakeryService) AddCaveat(m *macaroon.Macaroon, caveat checkers.Caveat) error {
   222  	s.MethodCall(s, "AddCaveat", m, caveat)
   223  	return s.NextErr()
   224  }
   225  
   226  func (s *mockBakeryService) CheckAny(ms []macaroon.Slice, assert map[string]string, checker checkers.Checker) (map[string]string, error) {
   227  	s.MethodCall(s, "CheckAny", ms, assert, checker)
   228  	return nil, s.NextErr()
   229  }
   230  
   231  func (s *mockBakeryService) NewMacaroon(caveats []checkers.Caveat) (*macaroon.Macaroon, error) {
   232  	s.MethodCall(s, "NewMacaroon", caveats)
   233  	return &macaroon.Macaroon{}, s.NextErr()
   234  }
   235  
   236  func (s *mockBakeryService) ExpireStorageAfter(t time.Duration) (authentication.ExpirableStorageBakeryService, error) {
   237  	s.MethodCall(s, "ExpireStorageAfter", t)
   238  	return s, s.NextErr()
   239  }
   240  
   241  type macaroonAuthenticatorSuite struct {
   242  	jujutesting.JujuConnSuite
   243  	// username holds the username that will be
   244  	// declared in the discharger's caveats.
   245  	username string
   246  }
   247  
   248  var _ = gc.Suite(&macaroonAuthenticatorSuite{})
   249  
   250  func (s *macaroonAuthenticatorSuite) Checker(req *http.Request, cond, arg string) ([]checkers.Caveat, error) {
   251  	return []checkers.Caveat{checkers.DeclaredCaveat("username", s.username)}, nil
   252  }
   253  
   254  var authenticateSuccessTests = []struct {
   255  	about              string
   256  	dischargedUsername string
   257  	finder             authentication.EntityFinder
   258  	expectTag          string
   259  	expectError        string
   260  }{{
   261  	about:              "user that can be found",
   262  	dischargedUsername: "bobbrown@somewhere",
   263  	expectTag:          "user-bobbrown@somewhere",
   264  	finder: simpleEntityFinder{
   265  		"user-bobbrown@somewhere": true,
   266  	},
   267  }, {
   268  	about:              "user with no @ domain",
   269  	dischargedUsername: "bobbrown",
   270  	finder: simpleEntityFinder{
   271  		"user-bobbrown@external": true,
   272  	},
   273  	expectTag: "user-bobbrown@external",
   274  }, {
   275  	about:              "user not found in database",
   276  	dischargedUsername: "bobbrown@nowhere",
   277  	finder:             simpleEntityFinder{},
   278  	expectError:        "invalid entity name or password",
   279  }, {
   280  	about:              "invalid user name",
   281  	dischargedUsername: "--",
   282  	finder:             simpleEntityFinder{},
   283  	expectError:        `"--" is an invalid user name`,
   284  }, {
   285  	about:              "ostensibly local name",
   286  	dischargedUsername: "cheat@local",
   287  	finder: simpleEntityFinder{
   288  		"cheat@local": true,
   289  	},
   290  	expectError: `external identity provider has provided ostensibly local name "cheat@local"`,
   291  }, {
   292  	about:              "FindEntity error",
   293  	dischargedUsername: "bobbrown@nowhere",
   294  	finder:             errorEntityFinder("lost in space"),
   295  	expectError:        "lost in space",
   296  }}
   297  
   298  func (s *macaroonAuthenticatorSuite) TestMacaroonAuthentication(c *gc.C) {
   299  	discharger := bakerytest.NewDischarger(nil, s.Checker)
   300  	defer discharger.Close()
   301  	for i, test := range authenticateSuccessTests {
   302  		c.Logf("\ntest %d; %s", i, test.about)
   303  		s.username = test.dischargedUsername
   304  
   305  		svc, err := bakery.NewService(bakery.NewServiceParams{
   306  			Locator: discharger,
   307  		})
   308  		c.Assert(err, jc.ErrorIsNil)
   309  		mac, err := svc.NewMacaroon(nil)
   310  		c.Assert(err, jc.ErrorIsNil)
   311  		authenticator := &authentication.ExternalMacaroonAuthenticator{
   312  			Service:          svc,
   313  			IdentityLocation: discharger.Location(),
   314  			Macaroon:         mac,
   315  		}
   316  
   317  		// Authenticate once to obtain the macaroon to be discharged.
   318  		_, err = authenticator.Authenticate(test.finder, nil, params.LoginRequest{
   319  			Credentials: "",
   320  			Nonce:       "",
   321  			Macaroons:   nil,
   322  		})
   323  
   324  		// Discharge the macaroon.
   325  		dischargeErr := errors.Cause(err).(*common.DischargeRequiredError)
   326  		client := httpbakery.NewClient()
   327  		ms, err := client.DischargeAll(dischargeErr.Macaroon)
   328  		c.Assert(err, jc.ErrorIsNil)
   329  
   330  		// Authenticate again with the discharged macaroon.
   331  		entity, err := authenticator.Authenticate(test.finder, nil, params.LoginRequest{
   332  			Credentials: "",
   333  			Nonce:       "",
   334  			Macaroons:   []macaroon.Slice{ms},
   335  		})
   336  		if test.expectError != "" {
   337  			c.Assert(err, gc.ErrorMatches, test.expectError)
   338  			c.Assert(entity, gc.Equals, nil)
   339  		} else {
   340  			c.Assert(err, jc.ErrorIsNil)
   341  			c.Assert(entity.Tag().String(), gc.Equals, test.expectTag)
   342  		}
   343  	}
   344  }
   345  
   346  type errorEntityFinder string
   347  
   348  func (f errorEntityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   349  	return nil, errors.New(string(f))
   350  }
   351  
   352  type simpleEntityFinder map[string]bool
   353  
   354  func (f simpleEntityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   355  	if utag, ok := tag.(names.UserTag); ok {
   356  		// It's a user tag which we need to be in canonical form
   357  		// so we can look it up unambiguously.
   358  		tag = names.NewUserTag(utag.Id())
   359  	}
   360  	if f[tag.String()] {
   361  		return &simpleEntity{tag}, nil
   362  	}
   363  	return nil, errors.NotFoundf("entity %q", tag)
   364  }
   365  
   366  type simpleEntity struct {
   367  	tag names.Tag
   368  }
   369  
   370  func (e *simpleEntity) Tag() names.Tag {
   371  	return e.tag
   372  }