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 }