func (d *Discharger) login(w http.ResponseWriter, r *http.Request) { r.ParseForm() if d.LoginHandler != nil { d.LoginHandler(d, w, r) return } al, err := d.GetAgentLogin(r) if err != nil { d.WriteJSON(w, http.StatusBadRequest, httpbakery.Error{ Message: fmt.Sprintf("cannot read agent login: %s", err), }) return } _, err = httpbakery.CheckRequest(d.Bakery, r, nil, nil) if err == nil { d.FinishWait(w, r, nil) d.WriteJSON(w, http.StatusOK, agent.AgentResponse{ AgentLogin: true, }) return } m, err := d.Bakery.NewMacaroon("", nil, []checkers.Caveat{ bakery.LocalThirdPartyCaveat(al.PublicKey), }) if err != nil { d.WriteJSON(w, http.StatusInternalServerError, httpbakery.Error{ Message: fmt.Sprintf("cannot create macaroon: %s", err), }) return } httpbakery.WriteDischargeRequiredError(w, m, "", nil) }
// userHandler handles requests to add new users, change user details, etc. // It is only accessible to users that are members of the admin group. func (h *handler) userHandler(p httprequest.Params) (interface{}, error) { ctxt := h.newContext(p.Request, "change-user") if _, err := httpbakery.CheckRequest(h.svc, p.Request, nil, ctxt); err != nil { // TODO do this only if the error cause is *bakery.VerificationError // We issue a macaroon with a third-party caveat targetting // the id service itself. This means that the flow for self-created // macaroons is just the same as for any other service. // Theoretically, we could just redirect the user to the // login page, but that would p.Requestuire a different flow // and it's not clear that it would be an advantage. m, err := h.svc.NewMacaroon("", nil, []checkers.Caveat{{ Location: h.svc.Location(), Condition: "member-of-group admin", }, { Condition: "operation change-user", }}) if err != nil { return nil, errgo.Notef(err, "cannot mint new macaroon") } return nil, &httpbakery.Error{ Message: err.Error(), Code: httpbakery.ErrDischargeRequired, Info: &httpbakery.ErrorInfo{ Macaroon: m, }, } } // PUT /user/$user - create new user // PUT /user/$user/group-membership - change group membership of user return nil, errgo.New("not implemented yet") }
// serverHandler returns an HTTP handler that checks macaroon authorization // and, if that succeeds, writes the string "done" and echos anything in the // request body. // It recognises the single first party caveat "is something". func serverHandler(hp serverHandlerParams) http.Handler { if hp.checker == nil { hp.checker = isChecker("something") } h := handleErrors(func(p httprequest.Params) error { if hp.alwaysReadBody { defer ioutil.ReadAll(p.Request.Body) } if _, checkErr := httpbakery.CheckRequest(hp.service, p.Request, nil, hp.checker); checkErr != nil { return newDischargeRequiredError(hp, checkErr, p.Request) } fmt.Fprintf(p.Response, "done") // Special case: the no-body path doesn't return the body. if p.Request.URL.Path == "/no-body" { return nil } data, err := ioutil.ReadAll(p.Request.Body) if err != nil { panic(fmt.Errorf("cannot read body: %v", err)) } if len(data) > 0 { fmt.Fprintf(p.Response, " %s", data) } return nil }) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { h(w, req, nil) }) }
func (s *ClientSuite) TestDoWithBodyAndCustomError(c *gc.C) { d := bakerytest.NewDischarger(nil, noCaveatChecker) defer d.Close() // Create a target service. svc := newService("loc", d) type customError struct { CustomError *httpbakery.Error } callCount := 0 handler := func(w http.ResponseWriter, req *http.Request) { callCount++ if _, checkErr := httpbakery.CheckRequest(svc, req, nil, checkers.New()); checkErr != nil { httprequest.WriteJSON(w, http.StatusTeapot, customError{ CustomError: newDischargeRequiredError(svc, d.Location(), nil, checkErr, req).(*httpbakery.Error), }) return } fmt.Fprintf(w, "hello there") } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() req, err := http.NewRequest("GET", srv.URL, nil) c.Assert(err, gc.IsNil) // First check that a normal request fails. resp, err := httpbakery.NewClient().Do(req) c.Assert(err, gc.IsNil) defer resp.Body.Close() c.Assert(resp.StatusCode, gc.Equals, http.StatusTeapot) c.Assert(callCount, gc.Equals, 1) callCount = 0 // Then check that a request with a custom error getter succeeds. errorGetter := func(resp *http.Response) error { if resp.StatusCode != http.StatusTeapot { return nil } data, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } var respErr customError if err := json.Unmarshal(data, &respErr); err != nil { panic(err) } return respErr.CustomError } resp, err = httpbakery.NewClient().DoWithBodyAndCustomError(req, nil, errorGetter) c.Assert(err, gc.IsNil) data, err := ioutil.ReadAll(resp.Body) c.Assert(err, gc.IsNil) c.Assert(string(data), gc.Equals, "hello there") c.Assert(callCount, gc.Equals, 2) }
func (srv *targetServiceHandler) serveSilver(w http.ResponseWriter, req *http.Request) { checker := srv.checkers(req, "silver") if _, err := httpbakery.CheckRequest(srv.svc, req, nil, checker); err != nil { srv.writeError(w, req, "silver", err) return } fmt.Fprintf(w, "every cloud has a silver lining") }
func (srv *targetServiceHandler) serveGold(w http.ResponseWriter, req *http.Request) { checker := srv.checkers(req, "gold") if _, err := httpbakery.CheckRequest(srv.svc, req, nil, checker); err != nil { srv.writeError(w, req, "gold", err) return } fmt.Fprintf(w, "all is golden") }
func (*suite) TestMacaraq(c *gc.C) { checked := false d := bakerytest.NewDischarger(nil, func(_ *http.Request, cond, arg string) ([]checkers.Caveat, error) { if cond != "something" { return nil, fmt.Errorf("unexpected 3rd party cond") } checked = true return nil, nil }) bsvc, err := bakery.NewService(bakery.NewServiceParams{ Location: "here", Locator: d, }) c.Assert(err, gc.IsNil) svc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.ParseForm() _, checkErr := httpbakery.CheckRequest(bsvc, req, nil, checkers.New()) if checkErr == nil { w.Header().Set("Content-Type", "application/json") data, err := json.Marshal(req.Form) c.Check(err, gc.IsNil) w.Write(data) return } m, err := bsvc.NewMacaroon("", nil, []checkers.Caveat{{ Location: d.Service.Location(), Condition: "something", }}) c.Check(err, gc.IsNil) httpbakery.WriteDischargeRequiredError(w, m, "/", checkErr) })) fset := flag.NewFlagSet("http", flag.ContinueOnError) ctxt, params, err := newContext(fset, []string{ svc.URL, "x=y", }) c.Assert(err, gc.IsNil) client := httpbakery.NewClient() resp, err := ctxt.doRequest(client, nil) c.Assert(err, gc.IsNil) defer resp.Body.Close() c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) c.Assert(checked, jc.IsTrue) var stdout bytes.Buffer err = showResponse(params, resp, &stdout) c.Assert(err, gc.IsNil) c.Assert(stdout.String(), gc.Equals, `{ x: [ "y" ] } `) }
func (s *ClientSuite) TestDischargeServerWithMacaraqOnDischarge(c *gc.C) { locator := bakery.NewPublicKeyRing() var called [3]int // create the services from leaf discharger to primary // service so that each one can know the location // to discharge at. key2, h2 := newHTTPDischarger(locator, func(svc *bakery.Service, req *http.Request, cavId, cav string) ([]checkers.Caveat, error) { called[2]++ if cav != "is-ok" { return nil, fmt.Errorf("unrecognized caveat at srv2") } return nil, nil }) srv2 := httptest.NewServer(h2) locator.AddPublicKeyForLocation(srv2.URL, true, key2) key1, h1 := newHTTPDischarger(locator, func(svc *bakery.Service, req *http.Request, cavId, cav string) ([]checkers.Caveat, error) { called[1]++ if _, err := httpbakery.CheckRequest(svc, req, nil, checkers.New()); err != nil { return nil, newDischargeRequiredError(serverHandlerParams{ service: svc, authLocation: srv2.URL, }, err, req) } if cav != "is-ok" { return nil, fmt.Errorf("unrecognized caveat at srv1") } return nil, nil }) srv1 := httptest.NewServer(h1) locator.AddPublicKeyForLocation(srv1.URL, true, key1) svc0 := newService("loc", locator) srv0 := httptest.NewServer(serverHandler(serverHandlerParams{ service: svc0, authLocation: srv1.URL, })) // Make a client request. client := httpbakery.NewClient() req, err := http.NewRequest("GET", srv0.URL, nil) c.Assert(err, gc.IsNil) resp, err := client.Do(req) c.Assert(err, gc.IsNil) defer resp.Body.Close() assertResponse(c, resp, "done") c.Assert(called, gc.DeepEquals, [3]int{0, 2, 1}) }
// canSpeakFor checks whether the client sending // the given request can speak for the given user. // We do that by declaring that user and checking // whether the supplied macaroons in the request // verify OK. func (ctxt *context) canSpeakFor(user string) error { if user == ctxt.declaredUser && ctxt.verifiedUser { // The context is a direct result of logging in. // No need to check macaroons. return nil } ctxt1 := *ctxt ctxt1.declaredUser = user _, err := httpbakery.CheckRequest(ctxt.svc, ctxt.req, nil, &ctxt1) if err != nil { log.Printf("client cannot speak for %q: %v", user, err) } else { log.Printf("client can speak for %q", user) } return err }
// 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 }
// serverHandler returns an HTTP handler that checks macaroon authorization // and, if that succeeds, writes the string "done" and echos anything in the // request body. // It recognises the single first party caveat "is something". func serverHandler(service *bakery.Service, authLocation string, cookiePath func() string) http.Handler { h := handleErrors(func(p httprequest.Params) error { if _, checkErr := httpbakery.CheckRequest(service, p.Request, nil, isChecker("something")); checkErr != nil { return newDischargeRequiredError(service, authLocation, cookiePath, checkErr, p.Request) } fmt.Fprintf(p.Response, "done") // Special case: the no-body path doesn't return the body. if p.Request.URL.Path == "/no-body" { return nil } data, err := ioutil.ReadAll(p.Request.Body) if err != nil { panic(fmt.Errorf("cannot read body: %v", err)) } if len(data) > 0 { fmt.Fprintf(p.Response, " %s", data) } return nil }) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { h(w, req, nil) }) }