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 }