Example #1
0
func (s *suite) TestMacaroonAuthorization(c *gc.C) {
	ch := charmRepo.CharmDir("wordpress")
	curl := charm.MustParseReference("~charmers/utopic/wordpress-42")
	purl := charm.MustParseReference("utopic/wordpress-42")
	err := s.client.UploadCharmWithRevision(curl, ch, 42)
	c.Assert(err, gc.IsNil)

	err = s.client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"})
	c.Assert(err, gc.IsNil)

	// Create a client without basic auth credentials
	client := csclient.New(csclient.Params{
		URL: s.srv.URL,
	})

	var result struct{ IdRevision struct{ Revision int } }
	// TODO 2015-01-23: once supported, rewrite the test using POST requests.
	_, err = client.Meta(purl, &result)
	c.Assert(err, gc.ErrorMatches, `cannot get "/utopic/wordpress-42/meta/any\?include=id-revision": cannot get discharge from ".*": third party refused discharge: cannot discharge: no discharge`)
	c.Assert(httpbakery.IsDischargeError(errgo.Cause(err)), gc.Equals, true)

	s.discharge = func(cond, arg string) ([]checkers.Caveat, error) {
		return []checkers.Caveat{checkers.DeclaredCaveat("username", "bob")}, nil
	}
	_, err = client.Meta(curl, &result)
	c.Assert(err, gc.IsNil)
	c.Assert(result.IdRevision.Revision, gc.Equals, curl.Revision)

	visitURL := "http://0.1.2.3/visitURL"
	s.discharge = func(cond, arg string) ([]checkers.Caveat, error) {
		return nil, &httpbakery.Error{
			Code:    httpbakery.ErrInteractionRequired,
			Message: "interaction required",
			Info: &httpbakery.ErrorInfo{
				VisitURL: visitURL,
				WaitURL:  "http://0.1.2.3/waitURL",
			}}
	}

	client = csclient.New(csclient.Params{
		URL: s.srv.URL,
		VisitWebPage: func(vurl *neturl.URL) error {
			c.Check(vurl.String(), gc.Equals, visitURL)
			return fmt.Errorf("stopping interaction")
		}})

	_, err = client.Meta(purl, &result)
	c.Assert(err, gc.ErrorMatches, `cannot get "/utopic/wordpress-42/meta/any\?include=id-revision": cannot get discharge from ".*": cannot start interactive session: stopping interaction`)
	c.Assert(result.IdRevision.Revision, gc.Equals, curl.Revision)
	c.Assert(httpbakery.IsInteractionError(errgo.Cause(err)), gc.Equals, true)
}
Example #2
0
func (s *ClientSuite) TestDischargeWithInteractionRequiredErrorAndWebPageVisitor(c *gc.C) {
	d := bakerytest.NewDischarger(nil, func(_ *http.Request, cond, arg string) ([]checkers.Caveat, error) {
		return nil, &httpbakery.Error{
			Code:    httpbakery.ErrInteractionRequired,
			Message: "interaction required",
			Info: &httpbakery.ErrorInfo{
				VisitURL: "http://0.1.2.3/",
				WaitURL:  "http://0.1.2.3/",
			},
		}
	})
	defer d.Close()

	// Create a target service.
	svc := newService("loc", d)

	ts := httptest.NewServer(serverHandler(serverHandlerParams{
		service:      svc,
		authLocation: d.Location(),
	}))
	defer ts.Close()

	// Create a client request.
	req, err := http.NewRequest("GET", ts.URL, nil)
	c.Assert(err, gc.IsNil)

	errCannotVisit := errgo.New("cannot visit")
	client := httpbakery.NewClient()
	client.WebPageVisitor = visitorFunc(func(_ *httpbakery.Client, m map[string]*url.URL) error {
		return errCannotVisit
	})

	// Make the request to the server.
	resp, err := client.Do(req)
	c.Assert(err, gc.ErrorMatches, `cannot get discharge from "https://.*": cannot start interactive session: cannot visit`)
	c.Assert(httpbakery.IsInteractionError(errgo.Cause(err)), gc.Equals, true)
	ierr, ok := errgo.Cause(err).(*httpbakery.InteractionError)
	c.Assert(ok, gc.Equals, true)
	c.Assert(ierr.Reason, gc.Equals, errCannotVisit)
	c.Assert(resp, gc.IsNil)
}
Example #3
0
// AddCharmWithAuthorization adds the given charm URL (which must include revision) to
// the environment, if it does not exist yet. Local charms are not
// supported, only charm store URLs. See also AddLocalCharm().
//
// The authorization macaroon, args.CharmStoreMacaroon, may be
// omitted, in which case this call is equivalent to AddCharm.
func AddCharmWithAuthorization(st *state.State, args params.AddCharmWithAuthorization) error {
	charmURL, err := charm.ParseURL(args.URL)
	if err != nil {
		return err
	}
	if charmURL.Schema != "cs" {
		return fmt.Errorf("only charm store charm URLs are supported, with cs: schema")
	}
	if charmURL.Revision < 0 {
		return fmt.Errorf("charm URL must include revision")
	}

	// First, check if a pending or a real charm exists in state.
	stateCharm, err := st.PrepareStoreCharmUpload(charmURL)
	if err != nil {
		return err
	}
	if stateCharm.IsUploaded() {
		// Charm already in state (it was uploaded already).
		return nil
	}

	// Get the charm and its information from the store.
	envConfig, err := st.EnvironConfig()
	if err != nil {
		return err
	}
	csURL, err := url.Parse(csclient.ServerURL)
	if err != nil {
		return err
	}
	csParams := charmrepo.NewCharmStoreParams{
		URL:        csURL.String(),
		HTTPClient: httpbakery.NewHTTPClient(),
	}
	if args.CharmStoreMacaroon != nil {
		// Set the provided charmstore authorizing macaroon
		// as a cookie in the HTTP client.
		// TODO discharge any third party caveats in the macaroon.
		ms := []*macaroon.Macaroon{args.CharmStoreMacaroon}
		httpbakery.SetCookie(csParams.HTTPClient.Jar, csURL, ms)
	}
	repo := config.SpecializeCharmRepo(
		NewCharmStore(csParams),
		envConfig,
	)
	downloadedCharm, err := repo.Get(charmURL)
	if err != nil {
		cause := errors.Cause(err)
		if httpbakery.IsDischargeError(cause) || httpbakery.IsInteractionError(cause) {
			return errors.NewUnauthorized(err, "")
		}
		return errors.Trace(err)
	}

	// Open it and calculate the SHA256 hash.
	downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive)
	if !ok {
		return errors.Errorf("expected a charm archive, got %T", downloadedCharm)
	}
	archive, err := os.Open(downloadedBundle.Path)
	if err != nil {
		return errors.Annotate(err, "cannot read downloaded charm")
	}
	defer archive.Close()
	bundleSHA256, size, err := utils.ReadSHA256(archive)
	if err != nil {
		return errors.Annotate(err, "cannot calculate SHA256 hash of charm")
	}
	if _, err := archive.Seek(0, 0); err != nil {
		return errors.Annotate(err, "cannot rewind charm archive")
	}

	// Store the charm archive in environment storage.
	return StoreCharmArchive(
		st,
		charmURL,
		downloadedCharm,
		archive,
		size,
		bundleSHA256,
	)
}
Example #4
0
// AddCharmWithAuthorization adds the given charm URL (which must include revision) to
// the environment, if it does not exist yet. Local charms are not
// supported, only charm store URLs. See also AddLocalCharm().
//
// The authorization macaroon, args.CharmStoreMacaroon, may be
// omitted, in which case this call is equivalent to AddCharm.
func AddCharmWithAuthorization(st *state.State, args params.AddCharmWithAuthorization) error {
	charmURL, err := charm.ParseURL(args.URL)
	if err != nil {
		return err
	}
	if charmURL.Schema != "cs" {
		return fmt.Errorf("only charm store charm URLs are supported, with cs: schema")
	}
	if charmURL.Revision < 0 {
		return fmt.Errorf("charm URL must include revision")
	}

	// First, check if a pending or a real charm exists in state.
	stateCharm, err := st.PrepareStoreCharmUpload(charmURL)
	if err != nil {
		return err
	}
	if stateCharm.IsUploaded() {
		// Charm already in state (it was uploaded already).
		return nil
	}

	// Open a charm store client.
	repo, err := openCSRepo(args)
	if err != nil {
		return err
	}
	envConfig, err := st.ModelConfig()
	if err != nil {
		return err
	}
	repo = config.SpecializeCharmRepo(repo, envConfig).(*charmrepo.CharmStore)

	// Get the charm and its information from the store.
	downloadedCharm, err := repo.Get(charmURL)
	if err != nil {
		cause := errors.Cause(err)
		if httpbakery.IsDischargeError(cause) || httpbakery.IsInteractionError(cause) {
			return errors.NewUnauthorized(err, "")
		}
		return errors.Trace(err)
	}

	if err := checkMinVersion(downloadedCharm); err != nil {
		return errors.Trace(err)
	}

	// Open it and calculate the SHA256 hash.
	downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive)
	if !ok {
		return errors.Errorf("expected a charm archive, got %T", downloadedCharm)
	}
	archive, err := os.Open(downloadedBundle.Path)
	if err != nil {
		return errors.Annotate(err, "cannot read downloaded charm")
	}
	defer archive.Close()
	bundleSHA256, size, err := utils.ReadSHA256(archive)
	if err != nil {
		return errors.Annotate(err, "cannot calculate SHA256 hash of charm")
	}
	if _, err := archive.Seek(0, 0); err != nil {
		return errors.Annotate(err, "cannot rewind charm archive")
	}

	// Store the charm archive in environment storage.
	return StoreCharmArchive(
		st,
		CharmArchive{
			ID:     charmURL,
			Charm:  downloadedCharm,
			Data:   archive,
			Size:   size,
			SHA256: bundleSHA256,
		},
	)
}