// Discharge creates a macaroon that discharges the third party caveat with the // given id that should have been created earlier using key.Public. The // condition implicit in the id is checked for validity using checker. If // it is valid, a new macaroon is returned which discharges the caveat // along with any caveats returned from the checker. func Discharge(key *KeyPair, checker ThirdPartyChecker, id string) (*macaroon.Macaroon, []checkers.Caveat, error) { decoder := newBoxDecoder(key) logger.Infof("server attempting to discharge %q", id) rootKey, condition, err := decoder.decodeCaveatId(id) if err != nil { return nil, nil, errgo.Notef(err, "discharger cannot decode caveat id") } // Note that we don't check the error - we allow the // third party checker to see even caveats that we can't // understand. cond, arg, _ := checkers.ParseCaveat(condition) var caveats []checkers.Caveat if cond == checkers.CondNeedDeclared { caveats, err = checkNeedDeclared(id, arg, checker) } else { caveats, err = checker.CheckThirdPartyCaveat(id, condition) } if err != nil { return nil, nil, errgo.Mask(err, errgo.Any) } // Note that the discharge macaroon does not need to // be stored persistently. Indeed, it would be a problem if // we did, because then the macaroon could potentially be used // for normal authorization with the third party. m, err := macaroon.New(rootKey, id, "") if err != nil { return nil, nil, errgo.Mask(err) } return m, caveats, nil }
// NewDischarger returns a new third party caveat discharger // which uses the given function to check caveats. // The cond and arg arguments to the function are as returned // by checkers.ParseCaveat. // // If locator is non-nil, it will be used to find public keys // for any third party caveats returned by the checker. // // Calling this function has the side-effect of setting // InsecureSkipVerify in http.DefaultTransport.TLSClientConfig // until all the dischargers are closed. func NewDischarger( locator bakery.PublicKeyLocator, checker func(req *http.Request, cond, arg string) ([]checkers.Caveat, error), ) *Discharger { mux := http.NewServeMux() server := httptest.NewTLSServer(mux) svc, err := bakery.NewService(bakery.NewServiceParams{ Location: server.URL, Locator: locator, }) if err != nil { panic(err) } checker1 := func(req *http.Request, cavId, cav string) ([]checkers.Caveat, error) { cond, arg, err := checkers.ParseCaveat(cav) if err != nil { return nil, err } return checker(req, cond, arg) } httpbakery.AddDischargeHandler(mux, "/", svc, checker1) startSkipVerify() return &Discharger{ Service: svc, server: server, } }
func (*CheckersSuite) TestOperationsChecker(c *gc.C) { for i, test := range operationsCheckerTests { c.Logf("%d: %s", i, test.about) cond, arg, err := checkers.ParseCaveat(test.caveat.Condition) c.Assert(err, gc.IsNil) c.Assert(test.oc.Condition(), gc.Equals, "") err = test.oc.Check(cond, arg) if test.expectError == "" { c.Assert(err, gc.IsNil) continue } c.Assert(err, gc.ErrorMatches, test.expectError) } }
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 }
// CheckLocalLoginCaveat parses and checks that the given caveat string is // valid for a local login request, and returns the tag of the local user // that the caveat asserts is logged in. checkers.ErrCaveatNotRecognized will // be returned if the caveat is not recognised. func CheckLocalLoginCaveat(caveat string) (names.UserTag, error) { var tag names.UserTag op, rest, err := checkers.ParseCaveat(caveat) if err != nil { return tag, errors.Annotatef(err, "cannot parse caveat %q", caveat) } if op != "is-authenticated-user" { return tag, checkers.ErrCaveatNotRecognized } if !names.IsValidUser(rest) { return tag, errors.NotValidf("username %q", rest) } tag = names.NewUserTag(rest) if !tag.IsLocal() { tag = names.UserTag{} return tag, errors.NotValidf("non-local username %q", rest) } return tag, nil }
func (ctxt *context) CheckThirdPartyCaveat(cavId, cav string) ([]checkers.Caveat, error) { h := ctxt.handler log.Printf("checking third party caveat %q", cav) op, rest, err := checkers.ParseCaveat(cav) if err != nil { return nil, fmt.Errorf("cannot parse caveat %q: %v", cav, err) } switch op { case "can-speak-for": // TODO(rog) We ignore the currently logged in user here, // but perhaps it would be better to let the user be in control // of which user they're currently "declared" as, rather than // getting privileges of users we currently have macaroons for. checkErr := ctxt.canSpeakFor(rest) if checkErr == nil { return ctxt.firstPartyCaveats(), nil } return nil, h.needLogin(cavId, cav, checkErr, ctxt.req) case "member-of-group": // The third-party caveat is asking if the currently logged in // user is a member of a particular group. // We can find the currently logged in user by checking // the username cookie (which doesn't provide any power, but // indicates which user name to check) if ctxt.declaredUser == "" { return nil, h.needLogin(cavId, cav, errgo.New("not logged in"), ctxt.req) } if err := ctxt.canSpeakFor(ctxt.declaredUser); err != nil { return nil, errgo.Notef(err, "cannot speak for declared user %q", ctxt.declaredUser) } info, ok := h.users[ctxt.declaredUser] if !ok { return nil, errgo.Newf("user %q not found", ctxt.declaredUser) } group := rest if !info.Groups[group] { return nil, errgo.Newf("not privileged enough") } return ctxt.firstPartyCaveats(), nil default: return nil, checkers.ErrCaveatNotRecognized } }
func objectID(ms macaroon.Slice) (string, error) { var fail string var id string for _, m := range ms { for _, cav := range m.Caveats() { cond, arg, err := checkers.ParseCaveat(cav.Id) if err != nil { // strange, but offtopic continue } if cond == "object" { if id == "" { id = arg } else { return fail, fmt.Errorf("multiple conflicting caveats") } } } } if id == "" { return fail, errors.New("not found") } return id, nil }