github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names"
    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/macaroon-bakery.v1/bakery"
    18  	"gopkg.in/macaroon-bakery.v1/bakery/checkers"
    19  	"gopkg.in/macaroon-bakery.v1/bakerytest"
    20  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    21  	"gopkg.in/macaroon.v1"
    22  
    23  	"github.com/juju/juju/apiserver/authentication"
    24  	"github.com/juju/juju/apiserver/common"
    25  	"github.com/juju/juju/apiserver/params"
    26  	jujutesting "github.com/juju/juju/juju/testing"
    27  	"github.com/juju/juju/state"
    28  	coretesting "github.com/juju/juju/testing"
    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.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    74  	unit, err := wordpress.AddUnit()
    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.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   128  	wordpressEP, err := wordpress.Endpoint("db")
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	mysql := s.AddTestingService(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  
   146  func (s *userAuthenticatorSuite) TestValidMacaroonUserLogin(c *gc.C) {
   147  	user := s.Factory.MakeUser(c, &factory.UserParams{
   148  		Name: "bobbrown",
   149  	})
   150  	macaroons := []macaroon.Slice{{&macaroon.Macaroon{}}}
   151  	service := mockBakeryService{}
   152  
   153  	// User login
   154  	authenticator := &authentication.UserAuthenticator{Service: &service}
   155  	_, err := authenticator.Authenticate(s.State, user.Tag(), params.LoginRequest{
   156  		Credentials: "",
   157  		Nonce:       "",
   158  		Macaroons:   macaroons,
   159  	})
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	service.CheckCallNames(c, "CheckAny")
   163  	call := service.Calls()[0]
   164  	c.Assert(call.Args, gc.HasLen, 3)
   165  	c.Assert(call.Args[0], jc.DeepEquals, macaroons)
   166  	c.Assert(call.Args[1], jc.DeepEquals, map[string]string{"username": "bobbrown@local"})
   167  	// no check for checker function, can't compare functions
   168  }
   169  
   170  func (s *userAuthenticatorSuite) TestCreateLocalLoginMacaroon(c *gc.C) {
   171  	service := mockBakeryService{}
   172  	clock := coretesting.NewClock(time.Time{})
   173  	authenticator := &authentication.UserAuthenticator{
   174  		Service: &service,
   175  		Clock:   clock,
   176  	}
   177  
   178  	_, err := authenticator.CreateLocalLoginMacaroon(names.NewUserTag("bobbrown"))
   179  	c.Assert(err, jc.ErrorIsNil)
   180  
   181  	service.CheckCallNames(c, "ExpireStorageAt", "NewMacaroon", "AddCaveat")
   182  	calls := service.Calls()
   183  	c.Assert(calls[0].Args, jc.DeepEquals, []interface{}{clock.Now().Add(24 * time.Hour)})
   184  	c.Assert(calls[1].Args, jc.DeepEquals, []interface{}{
   185  		"", []byte(nil), []checkers.Caveat{
   186  			checkers.DeclaredCaveat("username", "bobbrown@local"),
   187  		},
   188  	})
   189  	c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{
   190  		&macaroon.Macaroon{},
   191  		checkers.TimeBeforeCaveat(clock.Now().Add(24 * time.Hour)),
   192  	})
   193  }
   194  
   195  type mockBakeryService struct {
   196  	testing.Stub
   197  }
   198  
   199  func (s *mockBakeryService) AddCaveat(m *macaroon.Macaroon, caveat checkers.Caveat) error {
   200  	s.MethodCall(s, "AddCaveat", m, caveat)
   201  	return s.NextErr()
   202  }
   203  
   204  func (s *mockBakeryService) CheckAny(ms []macaroon.Slice, assert map[string]string, checker checkers.Checker) (map[string]string, error) {
   205  	s.MethodCall(s, "CheckAny", ms, assert, checker)
   206  	return nil, s.NextErr()
   207  }
   208  
   209  func (s *mockBakeryService) NewMacaroon(id string, key []byte, caveats []checkers.Caveat) (*macaroon.Macaroon, error) {
   210  	s.MethodCall(s, "NewMacaroon", id, key, caveats)
   211  	return &macaroon.Macaroon{}, s.NextErr()
   212  }
   213  
   214  func (s *mockBakeryService) ExpireStorageAt(t time.Time) (authentication.ExpirableStorageBakeryService, error) {
   215  	s.MethodCall(s, "ExpireStorageAt", t)
   216  	return s, s.NextErr()
   217  }
   218  
   219  type macaroonAuthenticatorSuite struct {
   220  	jujutesting.JujuConnSuite
   221  	// username holds the username that will be
   222  	// declared in the discharger's caveats.
   223  	username string
   224  }
   225  
   226  var _ = gc.Suite(&macaroonAuthenticatorSuite{})
   227  
   228  func (s *macaroonAuthenticatorSuite) Checker(req *http.Request, cond, arg string) ([]checkers.Caveat, error) {
   229  	return []checkers.Caveat{checkers.DeclaredCaveat("username", s.username)}, nil
   230  }
   231  
   232  var authenticateSuccessTests = []struct {
   233  	about              string
   234  	dischargedUsername string
   235  	finder             authentication.EntityFinder
   236  	expectTag          string
   237  	expectError        string
   238  }{{
   239  	about:              "user that can be found",
   240  	dischargedUsername: "bobbrown@somewhere",
   241  	expectTag:          "user-bobbrown@somewhere",
   242  	finder: simpleEntityFinder{
   243  		"user-bobbrown@somewhere": true,
   244  	},
   245  }, {
   246  	about:              "user with no @ domain",
   247  	dischargedUsername: "bobbrown",
   248  	finder: simpleEntityFinder{
   249  		"user-bobbrown@external": true,
   250  	},
   251  	expectTag: "user-bobbrown@external",
   252  }, {
   253  	about:              "user not found in database",
   254  	dischargedUsername: "bobbrown@nowhere",
   255  	finder:             simpleEntityFinder{},
   256  	expectError:        "invalid entity name or password",
   257  }, {
   258  	about:              "invalid user name",
   259  	dischargedUsername: "--",
   260  	finder:             simpleEntityFinder{},
   261  	expectError:        `"--" is an invalid user name`,
   262  }, {
   263  	about:              "ostensibly local name",
   264  	dischargedUsername: "cheat@local",
   265  	finder: simpleEntityFinder{
   266  		"cheat@local": true,
   267  	},
   268  	expectError: `external identity provider has provided ostensibly local name "cheat@local"`,
   269  }, {
   270  	about:              "FindEntity error",
   271  	dischargedUsername: "bobbrown@nowhere",
   272  	finder:             errorEntityFinder("lost in space"),
   273  	expectError:        "lost in space",
   274  }}
   275  
   276  func (s *macaroonAuthenticatorSuite) TestMacaroonAuthentication(c *gc.C) {
   277  	discharger := bakerytest.NewDischarger(nil, s.Checker)
   278  	defer discharger.Close()
   279  	for i, test := range authenticateSuccessTests {
   280  		c.Logf("\ntest %d; %s", i, test.about)
   281  		s.username = test.dischargedUsername
   282  
   283  		svc, err := bakery.NewService(bakery.NewServiceParams{
   284  			Locator: discharger,
   285  		})
   286  		c.Assert(err, jc.ErrorIsNil)
   287  		mac, err := svc.NewMacaroon("", nil, nil)
   288  		c.Assert(err, jc.ErrorIsNil)
   289  		authenticator := &authentication.ExternalMacaroonAuthenticator{
   290  			Service:          svc,
   291  			IdentityLocation: discharger.Location(),
   292  			Macaroon:         mac,
   293  		}
   294  
   295  		// Authenticate once to obtain the macaroon to be discharged.
   296  		_, err = authenticator.Authenticate(test.finder, nil, params.LoginRequest{
   297  			Credentials: "",
   298  			Nonce:       "",
   299  			Macaroons:   nil,
   300  		})
   301  
   302  		// Discharge the macaroon.
   303  		dischargeErr := errors.Cause(err).(*common.DischargeRequiredError)
   304  		client := httpbakery.NewClient()
   305  		ms, err := client.DischargeAll(dischargeErr.Macaroon)
   306  		c.Assert(err, jc.ErrorIsNil)
   307  
   308  		// Authenticate again with the discharged macaroon.
   309  		entity, err := authenticator.Authenticate(test.finder, nil, params.LoginRequest{
   310  			Credentials: "",
   311  			Nonce:       "",
   312  			Macaroons:   []macaroon.Slice{ms},
   313  		})
   314  		if test.expectError != "" {
   315  			c.Assert(err, gc.ErrorMatches, test.expectError)
   316  			c.Assert(entity, gc.Equals, nil)
   317  		} else {
   318  			c.Assert(err, jc.ErrorIsNil)
   319  			c.Assert(entity.Tag().String(), gc.Equals, test.expectTag)
   320  		}
   321  	}
   322  }
   323  
   324  type errorEntityFinder string
   325  
   326  func (f errorEntityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   327  	return nil, errors.New(string(f))
   328  }
   329  
   330  type simpleEntityFinder map[string]bool
   331  
   332  func (f simpleEntityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   333  	if utag, ok := tag.(names.UserTag); ok {
   334  		// It's a user tag which we need to be in canonical form
   335  		// so we can look it up unambiguously.
   336  		tag = names.NewUserTag(utag.Canonical())
   337  	}
   338  	if f[tag.String()] {
   339  		return &simpleEntity{tag}, nil
   340  	}
   341  	return nil, errors.NotFoundf("entity %q", tag)
   342  }
   343  
   344  type simpleEntity struct {
   345  	tag names.Tag
   346  }
   347  
   348  func (e *simpleEntity) Tag() names.Tag {
   349  	return e.tag
   350  }