func (d *InteractiveDischarger) wait(w http.ResponseWriter, r *http.Request) { r.ParseForm() d.mu.Lock() discharge, ok := d.waiting[r.Form.Get("waitid")] d.mu.Unlock() if !ok { code, body := httpbakery.ErrorToResponse(errgo.Newf("invalid waitid %q", r.Form.Get("waitid"))) httprequest.WriteJSON(w, code, body) return } defer func() { d.mu.Lock() delete(d.waiting, r.Form.Get("waitid")) d.mu.Unlock() }() var err error var cavs []checkers.Caveat select { case res := <-discharge.c: err = res.err cavs = res.cavs case <-time.After(5 * time.Minute): code, body := httpbakery.ErrorToResponse(errgo.New("timeout waiting for interaction to complete")) httprequest.WriteJSON(w, code, body) return } if err != nil { code, body := httpbakery.ErrorToResponse(err) httprequest.WriteJSON(w, code, body) return } m, err := d.Service.Discharge( bakery.ThirdPartyCheckerFunc( func(cavId, caveat string) ([]checkers.Caveat, error) { return cavs, nil }, ), discharge.cavId, ) if err != nil { code, body := httpbakery.ErrorToResponse(err) httprequest.WriteJSON(w, code, body) return } httprequest.WriteJSON( w, http.StatusOK, httpbakery.WaitResponse{ Macaroon: m, }, ) }
func (s *ErrorSuite) TestNewInteractionRequiredError(c *gc.C) { // With a request with no version header, the response // should be 407. req, err := http.NewRequest("GET", "/", nil) c.Assert(err, gc.IsNil) err = httpbakery.NewInteractionRequiredError("/visit", "/wait", nil, req) code, resp := httpbakery.ErrorToResponse(err) c.Assert(code, gc.Equals, http.StatusProxyAuthRequired) data, err := json.Marshal(resp) c.Assert(err, gc.IsNil) c.Assert(string(data), jc.JSONEquals, &httpbakery.Error{ Code: httpbakery.ErrInteractionRequired, Message: httpbakery.ErrInteractionRequired.Error(), Info: &httpbakery.ErrorInfo{ VisitURL: "/visit", WaitURL: "/wait", }, }) // With a request with a version 1 header, the response // should be 401. req.Header.Set("Bakery-Protocol-Version", "1") err = httpbakery.NewInteractionRequiredError("/visit", "/wait", nil, req) code, resp = httpbakery.ErrorToResponse(err) c.Assert(code, gc.Equals, http.StatusUnauthorized) h := make(http.Header) resp.(httprequest.HeaderSetter).SetHeader(h) c.Assert(h.Get("WWW-Authenticate"), gc.Equals, "Macaroon") data, err = json.Marshal(resp) c.Assert(err, gc.IsNil) c.Assert(string(data), jc.JSONEquals, &httpbakery.Error{ Code: httpbakery.ErrInteractionRequired, Message: httpbakery.ErrInteractionRequired.Error(), Info: &httpbakery.ErrorInfo{ VisitURL: "/visit", WaitURL: "/wait", }, }) }
// FinishInteraction signals to the InteractiveDischarger that a // particular interaction is complete. It causes any waiting requests to // return. If err is not nil then it will be returned by the // corresponding /wait request. func (d *InteractiveDischarger) FinishInteraction(w http.ResponseWriter, r *http.Request, cavs []checkers.Caveat, err error) { r.ParseForm() d.mu.Lock() discharge, ok := d.waiting[r.Form.Get("waitid")] d.mu.Unlock() if !ok { code, body := httpbakery.ErrorToResponse(errgo.Newf("invalid waitid %q", r.Form.Get("waitid"))) httprequest.WriteJSON(w, code, body) return } select { case discharge.c <- dischargeResult{err: err, cavs: cavs}: default: panic("cannot finish interaction " + r.Form.Get("waitid")) } return }