// CreateLocalLoginMacaroon creates a time-limited macaroon for a local user // to log into the controller with. The macaroon will be valid for use with // UserAuthenticator.Authenticate until the time limit expires, or the Juju // controller agent restarts. // // NOTE(axw) this method will generate a key for a previously unseen user, // and store it in the bakery.Service's storage. Callers should first ensure // the user is valid before calling this, to avoid filling storage with keys // for invalid users. func (u *UserAuthenticator) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { expiryTime := u.Clock.Now().Add(localLoginExpiryTime) // Ensure that the private key that we generate and store will be // removed from storage once the expiry time has elapsed. bakeryService, err := u.Service.ExpireStorageAt(expiryTime) if err != nil { return nil, errors.Trace(err) } // We create the macaroon with a random ID and random root key, which // enables multiple clients to login as the same user and obtain separate // macaroons without having them use the same root key. m, err := bakeryService.NewMacaroon("", nil, []checkers.Caveat{ // The macaroon may only be used to log in as the user // specified by the tag passed to CreateLocalUserMacaroon. checkers.DeclaredCaveat(usernameKey, tag.Canonical()), }) if err != nil { return nil, errors.Annotate(err, "cannot create macaroon") } if err := addMacaroonTimeBeforeCaveat(bakeryService, m, expiryTime); err != nil { return nil, errors.Trace(err) } return m, nil }
func (s *CharmStoreSuite) SetUpTest(c *gc.C) { s.CleanupSuite.SetUpTest(c) s.discharger = bakerytest.NewDischarger(nil, func(_ *http.Request, cond string, arg string) ([]checkers.Caveat, error) { if s.DischargeUser == "" { return nil, fmt.Errorf("discharge denied") } return []checkers.Caveat{ checkers.DeclaredCaveat("username", s.DischargeUser), }, nil }) db := s.Session.DB("juju-testing") params := charmstore.ServerParams{ AuthUsername: "******", AuthPassword: "******", IdentityLocation: s.discharger.Location(), PublicKeyLocator: s.discharger, } handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4) c.Assert(err, jc.ErrorIsNil) s.handler = handler s.Srv = httptest.NewServer(handler) s.Client = csclient.New(csclient.Params{ URL: s.Srv.URL, User: params.AuthUsername, Password: params.AuthPassword, }) s.PatchValue(&charmrepo.CacheDir, c.MkDir()) s.PatchValue(&service.NewCharmStore, func(p charmrepo.NewCharmStoreParams) charmrepo.Interface { p.URL = s.Srv.URL return charmrepo.NewCharmStore(p) }) }
func (s *charmStoreSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) // Set up the third party discharger. s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) { cookie, err := req.Cookie(clientUserCookie) if err != nil { return nil, errors.Annotate(err, "discharge denied to non-clients") } return []checkers.Caveat{ checkers.DeclaredCaveat("username", cookie.Value), }, nil }) // Set up the charm store testing server. db := s.Session.DB("juju-testing") params := charmstore.ServerParams{ AuthUsername: "******", AuthPassword: "******", IdentityLocation: s.discharger.Location(), PublicKeyLocator: s.discharger, } handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4) c.Assert(err, jc.ErrorIsNil) s.handler = handler s.srv = httptest.NewServer(handler) s.client = csclient.New(csclient.Params{ URL: s.srv.URL, User: params.AuthUsername, Password: params.AuthPassword, }) // Initialize the charm cache dir. s.PatchValue(&charmrepo.CacheDir, c.MkDir()) // Point the CLI to the charm store testing server. original := newCharmStoreClient s.PatchValue(&newCharmStoreClient, func() (*csClient, error) { csclient, err := original() if err != nil { return nil, err } csclient.params.URL = s.srv.URL // Add a cookie so that the discharger can detect whether the // HTTP client is the juju environment or the juju client. lurl, err := url.Parse(s.discharger.Location()) if err != nil { panic(err) } csclient.params.HTTPClient.Jar.SetCookies(lurl, []*http.Cookie{{ Name: clientUserCookie, Value: clientUserName, }}) return csclient, nil }) // Point the Juju API server to the charm store testing server. s.PatchValue(&csclient.ServerURL, s.srv.URL) }
func (s *suite) TestLogin(c *gc.C) { ch := charmRepo.CharmDir("wordpress") url := charm.MustParseReference("~charmers/utopic/wordpress-42") purl := charm.MustParseReference("utopic/wordpress-42") err := s.client.UploadCharmWithRevision(url, ch, 42) c.Assert(err, gc.IsNil) err = s.client.Put("/"+url.Path()+"/meta/perm/read", []string{"bob"}) c.Assert(err, gc.IsNil) httpClient := httpbakery.NewHTTPClient() client := csclient.New(csclient.Params{ URL: s.srv.URL, HTTPClient: httpClient, }) var result struct{ IdRevision struct{ Revision int } } _, err = client.Meta(purl, &result) c.Assert(err, gc.NotNil) // Try logging in when the discharger fails. err = client.Login() c.Assert(err, gc.ErrorMatches, `cannot retrieve the authentication macaroon: cannot get discharge from ".*": third party refused discharge: cannot discharge: no discharge`) // Allow the discharge. s.discharge = func(cond, arg string) ([]checkers.Caveat, error) { return []checkers.Caveat{checkers.DeclaredCaveat("username", "bob")}, nil } err = client.Login() c.Assert(err, gc.IsNil) // Change discharge so that we're sure the cookies are being // used rather than the discharge mechanism. s.discharge = func(cond, arg string) ([]checkers.Caveat, error) { return nil, fmt.Errorf("no discharge") } // Check that the request still works. _, err = client.Meta(purl, &result) c.Assert(err, gc.IsNil) c.Assert(result.IdRevision.Revision, gc.Equals, url.Revision) // Check that we've got one cookie. srvURL, err := neturl.Parse(s.srv.URL) c.Assert(err, gc.IsNil) c.Assert(httpClient.Jar.Cookies(srvURL), gc.HasLen, 1) // Log in again. err = client.Login() c.Assert(err, gc.IsNil) // Check that we still only have one cookie. c.Assert(httpClient.Jar.Cookies(srvURL), gc.HasLen, 1) }
func (m *mockapi) Authorize(modelUUID, charmURL, applicationName, plan string, visitWebPage func(*url.URL) error) (*macaroon.Macaroon, error) { err := m.NextErr() if err != nil { return nil, errors.Trace(err) } m.AddCall("Authorize", modelUUID, charmURL, applicationName) macaroon, err := m.service.NewMacaroon( "", nil, []checkers.Caveat{ checkers.DeclaredCaveat("environment", modelUUID), checkers.DeclaredCaveat("charm", charmURL), checkers.DeclaredCaveat("service", applicationName), checkers.DeclaredCaveat("plan", plan), }, ) if err != nil { return nil, errors.Trace(err) } m.macaroon = macaroon return m.macaroon, nil }
func (s *suite) TestMacaroonAuthorization(c *gc.C) { ch := charmRepo.CharmDir("wordpress") curl := charm.MustParseReference("~charmers/utopic/wordpress-42") purl := charm.MustParseReference("utopic/wordpress-42") err := s.client.UploadCharmWithRevision(curl, ch, 42) c.Assert(err, gc.IsNil) err = s.client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"}) c.Assert(err, gc.IsNil) // Create a client without basic auth credentials client := csclient.New(csclient.Params{ URL: s.srv.URL, }) var result struct{ IdRevision struct{ Revision int } } // TODO 2015-01-23: once supported, rewrite the test using POST requests. _, err = client.Meta(purl, &result) c.Assert(err, gc.ErrorMatches, `cannot get "/utopic/wordpress-42/meta/any\?include=id-revision": cannot get discharge from ".*": third party refused discharge: cannot discharge: no discharge`) c.Assert(httpbakery.IsDischargeError(errgo.Cause(err)), gc.Equals, true) s.discharge = func(cond, arg string) ([]checkers.Caveat, error) { return []checkers.Caveat{checkers.DeclaredCaveat("username", "bob")}, nil } _, err = client.Meta(curl, &result) c.Assert(err, gc.IsNil) c.Assert(result.IdRevision.Revision, gc.Equals, curl.Revision) visitURL := "http://0.1.2.3/visitURL" s.discharge = func(cond, arg string) ([]checkers.Caveat, error) { return nil, &httpbakery.Error{ Code: httpbakery.ErrInteractionRequired, Message: "interaction required", Info: &httpbakery.ErrorInfo{ VisitURL: visitURL, WaitURL: "http://0.1.2.3/waitURL", }} } client = csclient.New(csclient.Params{ URL: s.srv.URL, VisitWebPage: func(vurl *neturl.URL) error { c.Check(vurl.String(), gc.Equals, visitURL) return fmt.Errorf("stopping interaction") }}) _, err = client.Meta(purl, &result) c.Assert(err, gc.ErrorMatches, `cannot get "/utopic/wordpress-42/meta/any\?include=id-revision": cannot get discharge from ".*": cannot start interactive session: stopping interaction`) c.Assert(result.IdRevision.Revision, gc.Equals, curl.Revision) c.Assert(httpbakery.IsInteractionError(errgo.Cause(err)), gc.Equals, true) }
func (s *suite) TestWhoAmI(c *gc.C) { httpClient := httpbakery.NewHTTPClient() client := csclient.New(csclient.Params{ URL: s.srv.URL, HTTPClient: httpClient, }) response, err := client.WhoAmI() c.Assert(err, gc.ErrorMatches, `cannot retrieve whoami response: cannot get discharge from ".*": third party refused discharge: cannot discharge: no discharge`) s.discharge = func(cond, arg string) ([]checkers.Caveat, error) { return []checkers.Caveat{checkers.DeclaredCaveat("username", "bob")}, nil } response, err = client.WhoAmI() c.Assert(err, gc.IsNil) c.Assert(response.User, gc.Equals, "bob") }
func checkNeedDeclared(caveatId, arg string, checker ThirdPartyChecker) ([]checkers.Caveat, error) { i := strings.Index(arg, " ") if i <= 0 { return nil, errgo.Newf("need-declared caveat requires an argument, got %q", arg) } needDeclared := strings.Split(arg[0:i], ",") for _, d := range needDeclared { if d == "" { return nil, errgo.New("need-declared caveat with empty required attribute") } } if len(needDeclared) == 0 { return nil, fmt.Errorf("need-declared caveat with no required attributes") } caveats, err := checker.CheckThirdPartyCaveat(caveatId, arg[i+1:]) if err != nil { return nil, errgo.Mask(err, errgo.Any) } declared := make(map[string]bool) for _, cav := range caveats { if cav.Location != "" { continue } // Note that we ignore the error. We allow the service to // generate caveats that we don't understand here. cond, arg, _ := checkers.ParseCaveat(cav.Condition) if cond != checkers.CondDeclared { continue } parts := strings.SplitN(arg, " ", 2) if len(parts) != 2 { return nil, errgo.Newf("declared caveat has no value") } declared[parts[0]] = true } // Add empty declarations for everything mentioned in need-declared // that was not actually declared. for _, d := range needDeclared { if !declared[d] { caveats = append(caveats, checkers.DeclaredCaveat(d, "")) } } return caveats, nil }
func (s *MacaroonSuite) SetUpTest(c *gc.C) { s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond, arg string) ([]checkers.Caveat, error) { if cond != "is-authenticated-user" { return nil, errors.New("unknown caveat") } var username string if s.DischargerLogin != nil { username = s.DischargerLogin() } if username == "" { return nil, errors.New("login denied by discharger") } return []checkers.Caveat{checkers.DeclaredCaveat("username", username)}, nil }) s.JujuConnSuite.ConfigAttrs = map[string]interface{}{ config.IdentityURL: s.discharger.Location(), } s.JujuConnSuite.SetUpTest(c) }
// CreateLocalLoginMacaroon creates a time-limited macaroon for a local user // to log into the controller with. The macaroon will be valid for use with // UserAuthenticator.Authenticate until the time limit expires, or the Juju // controller agent restarts. // // NOTE(axw) this method will generate a key for a previously unseen user, // and store it in the bakery.Service's storage, which is currently in-memory. // Callers should first ensure the user is valid before calling this, to avoid // filling memory with keys for invalid users. func (u *UserAuthenticator) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) { // We create the macaroon with a random ID and random root key, which // enables multiple clients to login as the same user and obtain separate // macaroons without having them use the same root key. // // TODO(axw) check with rogpeppe about this. bakery.Service doesn't // currently garbage collect, so this will grow until the controller // agent restarts. m, err := u.Service.NewMacaroon("", nil, []checkers.Caveat{ // The macaroon may only be used to log in as the user // specified by the tag passed to CreateLocalUserMacaroon. checkers.DeclaredCaveat(usernameKey, tag.Canonical()), }) if err != nil { return nil, errors.Annotate(err, "cannot create macaroon") } if err := addMacaroonTimeBeforeCaveat(u.Service, m, localLoginExpiryTime); err != nil { return nil, errors.Trace(err) } return m, nil }
// CheckLocalLoginRequest checks that the given HTTP request contains at least // one valid local login macaroon minted by the given service using // CreateLocalLoginMacaroon. It returns an error with a // *bakery.VerificationError cause if the macaroon verification failed. If the // macaroon is valid, CheckLocalLoginRequest returns a list of caveats to add // to the discharge macaroon. func CheckLocalLoginRequest( service *bakery.Service, req *http.Request, tag names.UserTag, clock clock.Clock, ) ([]checkers.Caveat, error) { _, err := httpbakery.CheckRequest(service, req, nil, checkers.CheckerFunc{ // Having a macaroon with an is-authenticated-user // caveat is proof that the user is "logged in". "is-authenticated-user", func(cond, arg string) error { return nil }, }) if err != nil { return nil, errors.Trace(err) } firstPartyCaveats := []checkers.Caveat{ checkers.DeclaredCaveat("username", tag.Id()), checkers.TimeBeforeCaveat(clock.Now().Add(localLoginExpiryTime)), } return firstPartyCaveats, nil }
func (s *userAuthenticatorSuite) TestCreateLocalLoginMacaroon(c *gc.C) { service := mockBakeryService{} clock := coretesting.NewClock(time.Time{}) authenticator := &authentication.UserAuthenticator{ Service: &service, Clock: clock, } _, err := authenticator.CreateLocalLoginMacaroon(names.NewUserTag("bobbrown")) c.Assert(err, jc.ErrorIsNil) service.CheckCallNames(c, "ExpireStorageAt", "NewMacaroon", "AddCaveat") calls := service.Calls() c.Assert(calls[0].Args, jc.DeepEquals, []interface{}{clock.Now().Add(24 * time.Hour)}) c.Assert(calls[1].Args, jc.DeepEquals, []interface{}{ "", []byte(nil), []checkers.Caveat{ checkers.DeclaredCaveat("username", "bobbrown@local"), }, }) c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{ &macaroon.Macaroon{}, checkers.TimeBeforeCaveat(clock.Now().Add(24 * time.Hour)), }) }
func (s *ServiceSuite) TestDischargeTwoNeedDeclared(c *gc.C) { locator := make(bakery.PublicKeyLocatorMap) firstParty := newService(c, "first", locator) thirdParty := newService(c, "third", locator) // firstParty mints a macaroon with two third party caveats // with overlapping attributes. m, err := firstParty.NewMacaroon("", nil, []checkers.Caveat{ checkers.NeedDeclaredCaveat(checkers.Caveat{ Location: "third", Condition: "x", }, "foo", "bar"), checkers.NeedDeclaredCaveat(checkers.Caveat{ Location: "third", Condition: "y", }, "bar", "baz"), }) c.Assert(err, gc.IsNil) // The client asks for a discharge macaroon for each third party caveat. // Since no declarations are added by the discharger, d, err := bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) { return thirdParty.Discharge(bakery.ThirdPartyCheckerFunc(func(_, caveat string) ([]checkers.Caveat, error) { return nil, nil }), cav.Id) }) c.Assert(err, gc.IsNil) declared := checkers.InferDeclared(d) c.Assert(declared, gc.DeepEquals, checkers.Declared{ "foo": "", "bar": "", "baz": "", }) err = firstParty.Check(d, checkers.New(declared)) c.Assert(err, gc.IsNil) // If they return conflicting values, the discharge fails. // The client asks for a discharge macaroon for each third party caveat. // Since no declarations are added by the discharger, d, err = bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) { return thirdParty.Discharge(bakery.ThirdPartyCheckerFunc(func(_, caveat string) ([]checkers.Caveat, error) { switch caveat { case "x": return []checkers.Caveat{ checkers.DeclaredCaveat("foo", "fooval1"), }, nil case "y": return []checkers.Caveat{ checkers.DeclaredCaveat("foo", "fooval2"), checkers.DeclaredCaveat("baz", "bazval"), }, nil } return nil, fmt.Errorf("not matched") }), cav.Id) }) c.Assert(err, gc.IsNil) declared = checkers.InferDeclared(d) c.Assert(declared, gc.DeepEquals, checkers.Declared{ "bar": "", "baz": "bazval", }) err = firstParty.Check(d, checkers.New(declared)) c.Assert(err, gc.ErrorMatches, `verification failed: caveat "declared foo fooval1" not satisfied: got foo=null, expected "fooval1"`) }
func (s *charmStoreSuite) SetUpTest(c *gc.C) { s.JujuConnSuite.SetUpTest(c) // Set up the third party discharger. s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) { cookie, err := req.Cookie(clientUserCookie) if err != nil { return nil, errors.Annotate(err, "discharge denied to non-clients") } return []checkers.Caveat{ checkers.DeclaredCaveat("username", cookie.Value), }, nil }) s.termsDischargerError = nil // Set up the third party terms discharger. s.termsDischarger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) { s.termsString = arg return nil, s.termsDischargerError }) s.termsString = "" keyring := bakery.NewPublicKeyRing() pk, err := httpbakery.PublicKeyForLocation(http.DefaultClient, s.discharger.Location()) c.Assert(err, gc.IsNil) err = keyring.AddPublicKeyForLocation(s.discharger.Location(), true, pk) c.Assert(err, gc.IsNil) pk, err = httpbakery.PublicKeyForLocation(http.DefaultClient, s.termsDischarger.Location()) c.Assert(err, gc.IsNil) err = keyring.AddPublicKeyForLocation(s.termsDischarger.Location(), true, pk) c.Assert(err, gc.IsNil) // Set up the charm store testing server. db := s.Session.DB("juju-testing") params := charmstore.ServerParams{ AuthUsername: "******", AuthPassword: "******", IdentityLocation: s.discharger.Location(), PublicKeyLocator: keyring, TermsLocation: s.termsDischarger.Location(), } handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5) c.Assert(err, jc.ErrorIsNil) s.handler = handler s.srv = httptest.NewServer(handler) c.Logf("started charmstore on %v", s.srv.URL) s.client = csclient.New(csclient.Params{ URL: s.srv.URL, User: params.AuthUsername, Password: params.AuthPassword, }) // Initialize the charm cache dir. s.PatchValue(&charmrepo.CacheDir, c.MkDir()) // Point the CLI to the charm store testing server. s.PatchValue(&newCharmStoreClient, func(client *httpbakery.Client) *csclient.Client { // Add a cookie so that the discharger can detect whether the // HTTP client is the juju environment or the juju client. lurl, err := url.Parse(s.discharger.Location()) c.Assert(err, jc.ErrorIsNil) client.Jar.SetCookies(lurl, []*http.Cookie{{ Name: clientUserCookie, Value: clientUserName, }}) return csclient.New(csclient.Params{ URL: s.srv.URL, BakeryClient: client, }) }) // Point the Juju API server to the charm store testing server. s.PatchValue(&csclient.ServerURL, s.srv.URL) }
expectError: `caveat "time-before 2006-01-02T15:04:05.123Z" not satisfied: macaroon has expired`, }, { caveat: checkers.TimeBeforeCaveat(now.Add(-1)).Condition, expectError: `caveat "time-before 2006-01-02T15:04:05.122999999Z" not satisfied: macaroon has expired`, }, { caveat: `time-before bad-date`, expectError: `caveat "time-before bad-date" not satisfied: parsing time "bad-date" as "2006-01-02T15:04:05.999999999Z07:00": cannot parse "bad-date" as "2006"`, }, { caveat: checkers.TimeBeforeCaveat(now).Condition + " ", expectError: `caveat "time-before 2006-01-02T15:04:05.123Z " not satisfied: parsing time "2006-01-02T15:04:05.123Z ": extra text: `, }}, }, { about: "declared, no entries", checker: checkers.New(checkers.Declared{}), checks: []checkTest{{ caveat: checkers.DeclaredCaveat("a", "aval").Condition, expectError: `caveat "declared a aval" not satisfied: got a=null, expected "aval"`, }, { caveat: checkers.CondDeclared, expectError: `caveat "declared" not satisfied: declared caveat has no value`, }}, }, { about: "declared, some entries", checker: checkers.New(checkers.Declared{ "a": "aval", "b": "bval", "spc": " a b", }), checks: []checkTest{{ caveat: checkers.DeclaredCaveat("a", "aval").Condition, }, {
func (s *macaroonAuthenticatorSuite) Checker(req *http.Request, cond, arg string) ([]checkers.Caveat, error) { return []checkers.Caveat{checkers.DeclaredCaveat("username", s.username)}, nil }
func (s *ServiceSuite) TestNeedDeclared(c *gc.C) { locator := make(bakery.PublicKeyLocatorMap) firstParty := newService(c, "first", locator) thirdParty := newService(c, "third", locator) // firstParty mints a macaroon with a third-party caveat addressed // to thirdParty with a need-declared caveat. m, err := firstParty.NewMacaroon("", nil, []checkers.Caveat{ checkers.NeedDeclaredCaveat(checkers.Caveat{ Location: "third", Condition: "something", }, "foo", "bar"), }) c.Assert(err, gc.IsNil) // The client asks for a discharge macaroon for each third party caveat. d, err := bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) { return thirdParty.Discharge(strcmpChecker("something"), cav.Id) }) c.Assert(err, gc.IsNil) // The required declared attributes should have been added // to the discharge macaroons. declared := checkers.InferDeclared(d) c.Assert(declared, gc.DeepEquals, checkers.Declared{ "foo": "", "bar": "", }) // Make sure the macaroons actually check out correctly // when provided with the declared checker. err = firstParty.Check(d, checkers.New(declared)) c.Assert(err, gc.IsNil) // Try again when the third party does add a required declaration. // The client asks for a discharge macaroon for each third party caveat. d, err = bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) { checker := thirdPartyCheckerWithCaveats{ checkers.DeclaredCaveat("foo", "a"), checkers.DeclaredCaveat("arble", "b"), } return thirdParty.Discharge(checker, cav.Id) }) c.Assert(err, gc.IsNil) // One attribute should have been added, the other was already there. declared = checkers.InferDeclared(d) c.Assert(declared, gc.DeepEquals, checkers.Declared{ "foo": "a", "bar": "", "arble": "b", }) err = firstParty.Check(d, checkers.New(declared)) c.Assert(err, gc.IsNil) // Try again, but this time pretend a client is sneakily trying // to add another "declared" attribute to alter the declarations. d, err = bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) { checker := thirdPartyCheckerWithCaveats{ checkers.DeclaredCaveat("foo", "a"), checkers.DeclaredCaveat("arble", "b"), } m, err := thirdParty.Discharge(checker, cav.Id) c.Assert(err, gc.IsNil) // Sneaky client adds a first party caveat. m.AddFirstPartyCaveat(checkers.DeclaredCaveat("foo", "c").Condition) return m, nil }) c.Assert(err, gc.IsNil) declared = checkers.InferDeclared(d) c.Assert(declared, gc.DeepEquals, checkers.Declared{ "bar": "", "arble": "b", }) err = firstParty.Check(d, checkers.New(declared)) c.Assert(err, gc.ErrorMatches, `verification failed: caveat "declared foo a" not satisfied: got foo=null, expected "a"`) }
func (*ServiceSuite) TestCheckAnyWithNoMacaroons(c *gc.C) { svc := newService(c, "somewhere", nil) newMacaroons := func(caveats ...checkers.Caveat) macaroon.Slice { m, err := svc.NewMacaroon("", nil, caveats) c.Assert(err, gc.IsNil) return macaroon.Slice{m} } tests := []struct { about string macaroons []macaroon.Slice assert map[string]string checker checkers.Checker expectDeclared map[string]string expectError string }{{ about: "no macaroons", expectError: "verification failed: no macaroons", }, { about: "one macaroon, no caveats", macaroons: []macaroon.Slice{ newMacaroons(), }, }, { about: "one macaroon, one unrecognized caveat", macaroons: []macaroon.Slice{ newMacaroons(checkers.Caveat{ Condition: "bad", }), }, expectError: `verification failed: caveat "bad" not satisfied: caveat not recognized`, }, { about: "two macaroons, only one ok", macaroons: []macaroon.Slice{ newMacaroons(checkers.Caveat{ Condition: "bad", }), newMacaroons(), }, }, { about: "macaroon with declared caveats", macaroons: []macaroon.Slice{ newMacaroons( checkers.DeclaredCaveat("key1", "value1"), checkers.DeclaredCaveat("key2", "value2"), ), }, expectDeclared: map[string]string{ "key1": "value1", "key2": "value2", }, }, { about: "macaroon with declared values and asserted keys with wrong value", macaroons: []macaroon.Slice{ newMacaroons( checkers.DeclaredCaveat("key1", "value1"), checkers.DeclaredCaveat("key2", "value2"), ), }, assert: map[string]string{ "key1": "valuex", }, expectError: `verification failed: caveat "declared key1 value1" not satisfied: got key1="valuex", expected "value1"`, }, { about: "macaroon with declared values and asserted keys with correct value", macaroons: []macaroon.Slice{ newMacaroons( checkers.DeclaredCaveat("key1", "value1"), checkers.DeclaredCaveat("key2", "value2"), ), }, assert: map[string]string{ "key1": "value1", }, expectDeclared: map[string]string{ "key1": "value1", "key2": "value2", }, }} for i, test := range tests { c.Logf("test %d: %s", i, test.about) if test.expectDeclared == nil { test.expectDeclared = make(map[string]string) } if test.checker == nil { test.checker = checkers.New() } decl, err := svc.CheckAny(test.macaroons, test.assert, test.checker) if test.expectError != "" { c.Assert(err, gc.ErrorMatches, test.expectError) c.Assert(decl, gc.HasLen, 0) continue } c.Assert(err, gc.IsNil) c.Assert(decl, jc.DeepEquals, test.expectDeclared) } }