// VisitWebPage is part of the httpbakery.Visitor interface. func (v *Visitor) VisitWebPage(client *httpbakery.Client, methodURLs map[string]*url.URL) error { methodURL := methodURLs[authMethod] if methodURL == nil { return httpbakery.ErrMethodNotSupported } password, err := v.getPassword(v.username) if err != nil { return err } // POST to the URL with username and password. resp, err := client.PostForm(methodURL.String(), url.Values{ "user": {v.username}, "password": {password}, }) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { return nil } var jsonError httpbakery.Error if err := json.NewDecoder(resp.Body).Decode(&jsonError); err != nil { return errors.Annotate(err, "unmarshalling error") } return &jsonError }
func (r *RegisterMeteredCharm) registerMetrics(modelUUID, charmURL, serviceName, budget, limit string, client *httpbakery.Client) ([]byte, error) { if r.RegisterURL == "" { return nil, errors.Errorf("no metric registration url is specified") } registerURL, err := url.Parse(r.RegisterURL) if err != nil { return nil, errors.Trace(err) } registrationPost := metricRegistrationPost{ ModelUUID: modelUUID, CharmURL: charmURL, ApplicationName: serviceName, PlanURL: r.Plan, Budget: budget, Limit: limit, } buff := &bytes.Buffer{} encoder := json.NewEncoder(buff) err = encoder.Encode(registrationPost) if err != nil { return nil, errors.Trace(err) } req, err := http.NewRequest("POST", registerURL.String(), nil) if err != nil { return nil, errors.Trace(err) } req.Header.Set("Content-Type", "application/json") response, err := client.DoWithBody(req, bytes.NewReader(buff.Bytes())) if err != nil { return nil, errors.Trace(err) } defer response.Body.Close() if response.StatusCode == http.StatusOK { b, err := ioutil.ReadAll(response.Body) if err != nil { return nil, errors.Annotatef(err, "failed to read the response") } return b, nil } var respError struct { Error string `json:"error"` } err = json.NewDecoder(response.Body).Decode(&respError) if err != nil { return nil, errors.Errorf("authorization failed: http response is %d", response.StatusCode) } return nil, errors.Errorf("authorization failed: %s", respError.Error) }
func (ctxt *context) doRequest(client *httpbakery.Client, stdin io.Reader) (*http.Response, error) { req := &http.Request{ URL: ctxt.url, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Method: ctxt.method, Header: ctxt.header, } if len(ctxt.urlValues) > 0 { if req.URL.RawQuery != "" { req.URL.RawQuery += "&" } req.URL.RawQuery += ctxt.urlValues.Encode() } var body []byte switch { case len(ctxt.form) > 0: req.Header.Set("Content-Type", "application/x-www-form-urlencoded") body = []byte(ctxt.form.Encode()) case len(ctxt.jsonObj) > 0: data, err := json.Marshal(ctxt.jsonObj) if err != nil { return nil, fmt.Errorf("cannot marshal JSON: %v", err) } body = data case req.Method != "GET" && req.Method != "HEAD" && stdin != nil: // No fields specified and it looks like we need a body. // TODO check if it's seekable or make a temp file. data, err := ioutil.ReadAll(stdin) if err != nil { return nil, fmt.Errorf("error reading stdin: %v", err) } // TODO if we're expecting JSON, accept rjson too. body = data } req.ContentLength = int64(len(body)) resp, err := client.DoWithBody(req, bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("cannot do HTTP request: %v", err) } return resp, nil }
// SetUpAuth configures agent authentication on c. A cookie is created in // c's cookie jar containing credentials derived from the username and // c.Key. c.VisitWebPage is set to VisitWebPage(c). The return is // non-nil only if c.Key is nil. func SetUpAuth(c *httpbakery.Client, u *url.URL, username string) error { if c.Key == nil { return errgo.New("cannot set-up authentication: client key not configured") } SetCookie(c.Jar, u, username, &c.Key.Public) c.VisitWebPage = VisitWebPage(c) return nil }
func (r *RegisterMeteredCharm) registerMetrics(environmentUUID, charmURL, serviceName string, client *httpbakery.Client) ([]byte, error) { if r.RegisterURL == "" { return nil, errors.Errorf("no metric registration url is specified") } registerURL, err := url.Parse(r.RegisterURL) if err != nil { return nil, errors.Trace(err) } registrationPost := metricRegistrationPost{ ModelUUID: environmentUUID, CharmURL: charmURL, ServiceName: serviceName, PlanURL: r.Plan, } buff := &bytes.Buffer{} encoder := json.NewEncoder(buff) err = encoder.Encode(registrationPost) if err != nil { return nil, errors.Trace(err) } req, err := http.NewRequest("POST", registerURL.String(), nil) if err != nil { return nil, errors.Trace(err) } req.Header.Set("Content-Type", "application/json") response, err := client.DoWithBody(req, bytes.NewReader(buff.Bytes())) if err != nil { return nil, errors.Trace(err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, errors.Errorf("failed to register metrics: http response is %d", response.StatusCode) } b, err := ioutil.ReadAll(response.Body) if err != nil { return nil, errors.Annotatef(err, "failed to read the response") } return b, nil }
func (r *RegisterMeteredCharm) getCharmPlans(client *httpbakery.Client, cURL string) ([]string, error) { if r.QueryURL == "" { return nil, errors.Errorf("no plan query url specified") } qURL, err := url.Parse(r.QueryURL) if err != nil { return nil, errors.Trace(err) } query := qURL.Query() query.Set("charm-url", cURL) qURL.RawQuery = query.Encode() req, err := http.NewRequest("GET", qURL.String(), nil) if err != nil { return nil, errors.Trace(err) } response, err := client.Do(req) if err != nil { return nil, errors.Trace(err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, errors.Errorf("failed to query plans: http response is %d", response.StatusCode) } var planInfo []struct { URL string `json:"url"` } dec := json.NewDecoder(response.Body) err = dec.Decode(&planInfo) if err != nil { return nil, errors.Trace(err) } info := make([]string, len(planInfo)) for i, p := range planInfo { info[i] = p.URL } return info, nil }
func (r *RegisterMeteredCharm) getDefaultPlan(client *httpbakery.Client, cURL string) (string, error) { if r.QueryURL == "" { return "", errors.Errorf("no plan query url specified") } qURL, err := url.Parse(r.QueryURL + "/default") if err != nil { return "", errors.Trace(err) } query := qURL.Query() query.Set("charm-url", cURL) qURL.RawQuery = query.Encode() req, err := http.NewRequest("GET", qURL.String(), nil) if err != nil { return "", errors.Trace(err) } response, err := client.Do(req) if err != nil { return "", errors.Trace(err) } defer response.Body.Close() if response.StatusCode == http.StatusNotFound { return "", &noDefaultPlanError{cURL} } if response.StatusCode != http.StatusOK { return "", errors.Errorf("failed to query default plan: http response is %d", response.StatusCode) } var planInfo struct { URL string `json:"url"` } dec := json.NewDecoder(response.Body) err = dec.Decode(&planInfo) if err != nil { return "", errors.Trace(err) } return planInfo.URL, nil }
// client represents a client of the target service. // In this simple example, it just tries a GET // request, which will fail unless the client // has the required authorization. func clientRequest(client *httpbakery.Client, serverEndpoint string) (string, error) { // The Do function implements the mechanics // of actually gathering discharge macaroons // when required, and retrying the request // when necessary. req, err := http.NewRequest("GET", serverEndpoint, nil) if err != nil { return "", errgo.Notef(err, "cannot make new HTTP request") } resp, err := client.Do(req) if err != nil { return "", errgo.NoteMask(err, "GET failed", errgo.Any) } defer resp.Body.Close() // TODO(rog) unmarshal error data, err := ioutil.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("cannot read response: %v", err) } return string(data), nil }
func newAPIConnectionParams( store jujuclient.ClientStore, controllerName, modelName string, accountDetails *jujuclient.AccountDetails, bakery *httpbakery.Client, apiOpen api.OpenFunc, getPassword func(string) (string, error), ) (juju.NewAPIConnectionParams, error) { if controllerName == "" { return juju.NewAPIConnectionParams{}, errors.Trace(errNoNameSpecified) } var modelUUID string if modelName != "" { modelDetails, err := store.ModelByName(controllerName, modelName) if err != nil { return juju.NewAPIConnectionParams{}, errors.Trace(err) } modelUUID = modelDetails.ModelUUID } dialOpts := api.DefaultDialOpts() dialOpts.BakeryClient = bakery if accountDetails != nil { bakery.WebPageVisitor = httpbakery.NewMultiVisitor( authentication.NewVisitor(accountDetails.User, getPassword), bakery.WebPageVisitor, ) } return juju.NewAPIConnectionParams{ Store: store, ControllerName: controllerName, AccountDetails: accountDetails, ModelUUID: modelUUID, DialOpts: dialOpts, OpenAPI: apiOpen, }, nil }
func (s *ClientSuite) TestMacaroonCookiePath(c *gc.C) { svc := newService("loc", nil) cookiePath := "" ts := httptest.NewServer(serverHandler(svc, "", func() string { return cookiePath })) defer ts.Close() var client *httpbakery.Client doRequest := func() { req, err := http.NewRequest("GET", ts.URL+"/foo/bar/", nil) c.Assert(err, gc.IsNil) client = httpbakery.NewClient() resp, err := client.Do(req) c.Assert(err, gc.IsNil) defer resp.Body.Close() assertResponse(c, resp, "done") } assertCookieCount := func(path string, n int) { u, err := url.Parse(ts.URL + path) c.Assert(err, gc.IsNil) c.Assert(client.Jar.Cookies(u), gc.HasLen, n) } cookiePath = "" c.Logf("- cookie path %q", cookiePath) doRequest() assertCookieCount("", 0) assertCookieCount("/foo", 0) assertCookieCount("/foo", 0) assertCookieCount("/foo/", 0) assertCookieCount("/foo/bar/", 1) assertCookieCount("/foo/bar/baz", 1) cookiePath = "/foo/" c.Logf("- cookie path %q", cookiePath) doRequest() assertCookieCount("", 0) assertCookieCount("/foo", 0) assertCookieCount("/foo/", 1) assertCookieCount("/foo/bar/", 1) assertCookieCount("/foo/bar/baz", 1) cookiePath = "/foo" c.Logf("- cookie path %q", cookiePath) doRequest() assertCookieCount("", 0) assertCookieCount("/bar", 0) assertCookieCount("/foo", 1) assertCookieCount("/foo/", 1) assertCookieCount("/foo/bar/", 1) assertCookieCount("/foo/bar/baz", 1) cookiePath = "../" c.Logf("- cookie path %q", cookiePath) doRequest() assertCookieCount("", 0) assertCookieCount("/bar", 0) assertCookieCount("/foo", 0) assertCookieCount("/foo/", 1) assertCookieCount("/foo/bar/", 1) assertCookieCount("/foo/bar/baz", 1) cookiePath = "../bar" c.Logf("- cookie path %q", cookiePath) doRequest() assertCookieCount("", 0) assertCookieCount("/bar", 0) assertCookieCount("/foo", 0) assertCookieCount("/foo/", 0) assertCookieCount("/foo/bar/", 1) assertCookieCount("/foo/bar/baz", 1) assertCookieCount("/foo/baz", 0) assertCookieCount("/foo/baz/", 0) assertCookieCount("/foo/baz/bar", 0) cookiePath = "/" c.Logf("- cookie path %q", cookiePath) doRequest() assertCookieCount("", 1) assertCookieCount("/bar", 1) assertCookieCount("/foo", 1) assertCookieCount("/foo/", 1) assertCookieCount("/foo/bar/", 1) assertCookieCount("/foo/bar/baz", 1) }
// SetUpAuth configures form authentication on c. The VisitWebPage field // in c will be set to a function that will attempt form-based // authentication using f to perform the interaction with the user and // fall back to using the current value of VisitWebPage if form-based // authentication is not supported. func SetUpAuth(c *httpbakery.Client, f form.Filler) { c.VisitWebPage = VisitWebPage(c, f, c.VisitWebPage) }
// VisitWebPage creates a function that can be used with // httpbakery.Client.VisitWebPage. The function uses c to access the // visit URL. If no agent-login cookie has been configured for u an error // with the cause of ErrNoAgentLoginCookie will be returned. If the login // fails the returned error will be of type *httpbakery.Error. If the // response from the visitURL cannot be interpreted the error will be of // type *UnexpectedResponseError. // // If using SetUpAuth, it should not be necessary to use // this function. func VisitWebPage(c *httpbakery.Client) func(u *url.URL) error { return func(u *url.URL) error { err := ErrNoAgentLoginCookie for _, c := range c.Jar.Cookies(u) { if c.Name == cookieName { err = nil break } } if err != nil { return errgo.WithCausef(err, http.ErrNoCookie, "cannot perform agent login") } req, err := http.NewRequest("GET", u.String(), nil) if err != nil { return errgo.Notef(err, "cannot create request") } resp, err := c.Do(req) if err != nil { return errgo.Notef(err, "cannot perform request") } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { logger.Errorf("cannot read response body: %s", err) b = []byte{} } mt, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) if err != nil { logger.Warningf("cannot parse response content type: %s", err) mt = "" } if mt != "application/json" { uerr := (*UnexpectedResponseError)(resp) uerr.Body = ioutil.NopCloser(bytes.NewReader(b)) return uerr } if resp.StatusCode != http.StatusOK { var herr httpbakery.Error err := json.Unmarshal(b, &herr) if err == nil && herr.Message != "" { return &herr } if err != nil { logger.Warningf("cannot unmarshal error response: %s", err) } uerr := (*UnexpectedResponseError)(resp) uerr.Body = ioutil.NopCloser(bytes.NewReader(b)) return uerr } var ar agentResponse err = json.Unmarshal(b, &ar) if err == nil && ar.AgentLogin { return nil } if err != nil { logger.Warningf("cannot unmarshal response: %s", err) } uerr := (*UnexpectedResponseError)(resp) uerr.Body = ioutil.NopCloser(bytes.NewReader(b)) return uerr } }