func (*VisitorSuite) TestMultiVisitorSequence(c *gc.C) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{"method": "http://somewhere/something"}`) })) defer srv.Close() firstCalled, secondCalled := 0, 0 v := httpbakery.NewMultiVisitor( visitorFunc(func(_ *httpbakery.Client, m map[string]*url.URL) error { c.Check(m["method"], gc.NotNil) firstCalled++ return httpbakery.ErrMethodNotSupported }), visitorFunc(func(_ *httpbakery.Client, m map[string]*url.URL) error { c.Check(m["method"], gc.NotNil) secondCalled++ return nil }), ) err := v.VisitWebPage(httpbakery.NewClient(), map[string]*url.URL{ httpbakery.UserInteractionMethod: mustParseURL(srv.URL), }) c.Assert(err, gc.IsNil) c.Assert(firstCalled, gc.Equals, 1) c.Assert(secondCalled, gc.Equals, 1) }
func (*VisitorSuite) TestUserInteractionFallback(c *gc.C) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{"method": "http://somewhere/something"}`) })) defer srv.Close() called := 0 // Check that even though the methods didn't explicitly // include the "interactive" method, it is still supplied. v := httpbakery.NewMultiVisitor( visitorFunc(func(_ *httpbakery.Client, m map[string]*url.URL) error { c.Check(m, jc.DeepEquals, map[string]*url.URL{ "method": mustParseURL("http://somewhere/something"), httpbakery.UserInteractionMethod: mustParseURL(srv.URL), }) called++ return nil }), ) err := v.VisitWebPage(httpbakery.NewClient(), map[string]*url.URL{ httpbakery.UserInteractionMethod: mustParseURL(srv.URL), }) c.Assert(err, gc.IsNil) c.Assert(called, gc.Equals, 1) }
func (*VisitorSuite) TestMultiVisitorVisitorError(c *gc.C) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{"method": "http://somewhere/something"}`) })) defer srv.Close() testError := errgo.New("test error") v := httpbakery.NewMultiVisitor( visitorFunc(func(*httpbakery.Client, map[string]*url.URL) error { return testError }), ) err := v.VisitWebPage(httpbakery.NewClient(), map[string]*url.URL{ httpbakery.UserInteractionMethod: mustParseURL(srv.URL), }) c.Assert(errgo.Cause(err), gc.Equals, testError) }
func (s *toolsWithMacaroonsSuite) TestCanPostWithLocalLogin(c *gc.C) { // Create a new local user that we can log in as // using macaroon authentication. const password = "******" user := s.Factory.MakeUser(c, &factory.UserParams{Password: password}) // Install a "web-page" visitor that deals with the interaction // method that Juju controllers support for authenticating local // users. Note: the use of httpbakery.NewMultiVisitor is necessary // to trigger httpbakery to query the authentication methods and // bypass browser authentication. var prompted bool jar := apitesting.NewClearableCookieJar() client := utils.GetNonValidatingHTTPClient() client.Jar = jar bakeryClient := httpbakery.NewClient() bakeryClient.Client = client bakeryClient.WebPageVisitor = httpbakery.NewMultiVisitor(apiauthentication.NewVisitor( user.UserTag().Id(), func(username string) (string, error) { c.Assert(username, gc.Equals, user.UserTag().Id()) prompted = true return password, nil }, )) bakeryDo := func(req *http.Request) (*http.Response, error) { var body io.ReadSeeker if req.Body != nil { body = req.Body.(io.ReadSeeker) req.Body = nil } return bakeryClient.DoWithBodyAndCustomError(req, body, bakeryGetError) } resp := s.sendRequest(c, httpRequestParams{ method: "POST", url: s.toolsURI(c, ""), tag: user.UserTag().String(), password: "", // no password forces macaroon usage do: bakeryDo, }) s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected binaryVersion argument") c.Assert(prompted, jc.IsTrue) }
func (c *changePasswordCommand) recordMacaroon(user, password string) error { accountDetails := &jujuclient.AccountDetails{User: user} args, err := c.NewAPIConnectionParams( c.ClientStore(), c.ControllerName(), "", accountDetails, ) if err != nil { return errors.Trace(err) } args.DialOpts.BakeryClient.WebPageVisitor = httpbakery.NewMultiVisitor( authentication.NewVisitor(accountDetails.User, func(string) (string, error) { return password, nil }), args.DialOpts.BakeryClient.WebPageVisitor, ) api, err := c.newAPIConnection(args) if err != nil { return errors.Annotate(err, "connecting to API") } return api.Close() }
func (s *formSuite) TestFormLogin(c *gc.C) { d := &formDischarger{} d.discharger = bakerytest.NewInteractiveDischarger(nil, http.HandlerFunc(d.visit)) defer d.discharger.Close() d.discharger.Mux.Handle("/form", http.HandlerFunc(d.form)) svc, err := bakery.NewService(bakery.NewServiceParams{ Locator: d.discharger, }) c.Assert(err, gc.IsNil) for i, test := range formLoginTests { c.Logf("test %d: %s", i, test.about) d.dischargeOptions = test.opts m, err := svc.NewMacaroon("", nil, []checkers.Caveat{{ Location: d.discharger.Location(), Condition: "test condition", }}) c.Assert(err, gc.Equals, nil) client := httpbakery.NewClient() filler := defaultFiller if test.filler != nil { filler = test.filler } handlers := []httpbakery.Visitor{ form.Visitor{ Filler: filler, }, } if test.fallback != nil { handlers = append(handlers, test.fallback) } client.WebPageVisitor = httpbakery.NewMultiVisitor(handlers...) ms, err := client.DischargeAll(m) if test.expectError != "" { c.Assert(err, gc.ErrorMatches, test.expectError) continue } c.Assert(err, gc.IsNil) c.Assert(len(ms), gc.Equals, 2) } }
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 *formSuite) TestFormTitle(c *gc.C) { d := &formDischarger{} d.discharger = bakerytest.NewInteractiveDischarger(nil, http.HandlerFunc(d.visit)) defer d.discharger.Close() d.discharger.Mux.Handle("/form", http.HandlerFunc(d.form)) svc, err := bakery.NewService(bakery.NewServiceParams{ Locator: testLocator{ loc: d.discharger.Location(), locator: d.discharger, }, }) c.Assert(err, gc.IsNil) for i, test := range formTitleTests { c.Logf("test %d: %s", i, test.host) m, err := svc.NewMacaroon("", nil, []checkers.Caveat{{ Location: "https://" + test.host, Condition: "test condition", }}) c.Assert(err, gc.Equals, nil) client := httpbakery.NewClient() c.Logf("match %v; replace with %v", test.host, d.discharger.Location()) client.Client.Transport = httptesting.URLRewritingTransport{ MatchPrefix: "https://" + test.host, Replace: d.discharger.Location(), RoundTripper: http.DefaultTransport, } var f titleTestFiller client.WebPageVisitor = httpbakery.NewMultiVisitor( form.Visitor{ Filler: &f, }, ) ms, err := client.DischargeAll(m) c.Assert(err, gc.IsNil) c.Assert(len(ms), gc.Equals, 2) c.Assert(f.title, gc.Equals, test.expect) } }
// NewAPIContext returns an API context that will use the given // context for user interactions when authorizing. // The returned API context must be closed after use. // // If ctxt is nil, no command-line authorization // will be supported. // // This function is provided for use by commands that cannot use // JujuCommandBase. Most clients should use that instead. func NewAPIContext(ctxt *cmd.Context, opts *AuthOpts) (*APIContext, error) { jar, err := cookiejar.New(&cookiejar.Options{ Filename: cookieFile(), }) if err != nil { return nil, errors.Trace(err) } var visitors []httpbakery.Visitor if ctxt != nil && opts != nil && opts.NoBrowser { filler := &form.IOFiller{ In: ctxt.Stdin, Out: ctxt.Stdout, } visitors = append(visitors, ussologin.NewVisitor("juju", filler, jujuclient.NewTokenStore())) } else { visitors = append(visitors, httpbakery.WebBrowserVisitor) } webPageVisitor := httpbakery.NewMultiVisitor(visitors...) return &APIContext{ Jar: jar, WebPageVisitor: webPageVisitor, }, nil }
func (*VisitorSuite) TestMultiVisitorNoInteractionMethods(c *gc.C) { initialPage := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/html") initialPage++ fmt.Fprint(w, `<html>oh yes</html>`) })) defer srv.Close() methods := map[string]*url.URL{ httpbakery.UserInteractionMethod: mustParseURL(srv.URL), } visited := 0 v := httpbakery.NewMultiVisitor( visitorFunc(func(_ *httpbakery.Client, m map[string]*url.URL) error { c.Check(m, jc.DeepEquals, methods) visited++ return nil }), ) err := v.VisitWebPage(httpbakery.NewClient(), methods) c.Assert(err, gc.IsNil) c.Assert(initialPage, gc.Equals, 1) c.Assert(visited, gc.Equals, 1) }
func (*VisitorSuite) TestMultiVisitorNoUserInteractionMethod(c *gc.C) { v := httpbakery.NewMultiVisitor() err := v.VisitWebPage(httpbakery.NewClient(), nil) c.Assert(err, gc.ErrorMatches, `cannot get interaction methods because no "interactive" URL found`) }