// stateForRequestAuthenticated returns a state instance appropriate for // using for the model implicit in the given request. // It also returns the authenticated entity. func (ctxt *httpContext) stateForRequestAuthenticated(r *http.Request) (*state.State, state.Entity, error) { st, err := ctxt.stateForRequestUnauthenticated(r) if err != nil { return nil, nil, errors.Trace(err) } req, err := ctxt.loginRequest(r) if err != nil { return nil, nil, errors.NewUnauthorized(err, "") } authenticator := ctxt.srv.authCtxt.authenticator(r.Host) entity, _, err := checkCreds(st, req, true, authenticator) if err != nil { if common.IsDischargeRequiredError(err) { return nil, nil, errors.Trace(err) } // Handle the special case of a worker on a controller machine // acting on behalf of a hosted model. if isMachineTag(req.AuthTag) { entity, err := checkControllerMachineCreds(ctxt.srv.state, req, authenticator) if err != nil { return nil, nil, errors.NewUnauthorized(err, "") } return st, entity, nil } // Any other error at this point should be treated as // "unauthorized". return nil, nil, errors.Trace(errors.NewUnauthorized(err, "")) } return st, entity, nil }
// RestoreError makes a best effort at converting the given error // back into an error originally converted by ServerError(). If the // error could not be converted then false is returned. func RestoreError(err error) (error, bool) { err = errors.Cause(err) if apiErr, ok := err.(*params.Error); !ok { return err, false } else if apiErr == nil { return nil, true } if params.ErrCode(err) == "" { return err, false } msg := err.Error() if singleton, ok := singletonError(err); ok { return singleton, true } // TODO(ericsnow) Support the other error types handled by ServerError(). switch { case params.IsCodeUnauthorized(err): return errors.NewUnauthorized(nil, msg), true case params.IsCodeNotFound(err): // TODO(ericsnow) UnknownModelError should be handled here too. // ...by parsing msg? return errors.NewNotFound(nil, msg), true case params.IsCodeAlreadyExists(err): return errors.NewAlreadyExists(nil, msg), true case params.IsCodeNotAssigned(err): return errors.NewNotAssigned(nil, msg), true case params.IsCodeHasAssignedUnits(err): // TODO(ericsnow) Handle state.HasAssignedUnitsError here. // ...by parsing msg? return err, false case params.IsCodeNoAddressSet(err): // TODO(ericsnow) Handle isNoAddressSetError here. // ...by parsing msg? return err, false case params.IsCodeNotProvisioned(err): return errors.NewNotProvisioned(nil, msg), true case params.IsCodeUpgradeInProgress(err): // TODO(ericsnow) Handle state.UpgradeInProgressError here. // ...by parsing msg? return err, false case params.IsCodeMachineHasAttachedStorage(err): // TODO(ericsnow) Handle state.HasAttachmentsError here. // ...by parsing msg? return err, false case params.IsCodeNotSupported(err): return errors.NewNotSupported(nil, msg), true case params.IsBadRequest(err): return errors.NewBadRequest(nil, msg), true case params.IsMethodNotAllowed(err): return errors.NewMethodNotAllowed(nil, msg), true case params.ErrCode(err) == params.CodeDischargeRequired: // TODO(ericsnow) Handle DischargeRequiredError here. return err, false default: return err, false } }
func (s *controllerSuite) TestWaitForAgentAPIReadyStopsRetriesWithOpenErr(c *gc.C) { s.mockBlockClient.numRetries = 0 s.mockBlockClient.retryCount = 0 s.mockBlockClient.loginError = errors.NewUnauthorized(nil, "") err := WaitForAgentInitialisation(cmdtesting.NullContext(c), nil, "controller") c.Check(err, jc.Satisfies, errors.IsUnauthorized) c.Check(s.mockBlockClient.retryCount, gc.Equals, 0) }
// stateForRequestAuthenticated returns a state instance appropriate for // using for the model implicit in the given request. // It also returns the authenticated entity. func (ctxt *httpContext) stateForRequestAuthenticated(r *http.Request) (*state.State, state.Entity, error) { st, err := ctxt.stateForRequestUnauthenticated(r) if err != nil { return nil, nil, errors.Trace(err) } req, err := ctxt.loginRequest(r) if err != nil { return nil, nil, errors.NewUnauthorized(err, "") } entity, _, err := checkCreds(st, req, true, ctxt.srv.authCtxt) if err != nil { // All errors other than a macaroon-discharge error count as // unauthorized at this point. if !common.IsDischargeRequiredError(err) { err = errors.NewUnauthorized(err, "") } return nil, nil, errors.Trace(err) } return st, entity, nil }
func (s *controllerSuite) TestWaitForAgentAPIReadyStopsRetriesWithOpenErr(c *gc.C) { s.mockBlockClient.numRetries = 0 s.mockBlockClient.retryCount = 0 s.mockBlockClient.loginError = errors.NewUnauthorized(nil, "") cmd := &modelcmd.ModelCommandBase{} cmd.SetClientStore(jujuclienttesting.NewMemStore()) err := WaitForAgentInitialisation(cmdtesting.NullContext(c), cmd, "controller", "default") c.Check(err, jc.Satisfies, errors.IsUnauthorized) c.Check(s.mockBlockClient.retryCount, gc.Equals, 0) }
func (s *BootstrapSuite) TestBootstrapAPIReadyStopsRetriesWithOpenErr(c *gc.C) { s.PatchValue(&bootstrapReadyPollDelay, 1*time.Millisecond) s.PatchValue(&bootstrapReadyPollCount, 5) defaultSeriesVersion := jujuversion.Current // Force a dev version by having a non zero build number. // This is because we have not uploaded any tools and auto // upload is only enabled for dev versions. defaultSeriesVersion.Build = 1234 s.PatchValue(&jujuversion.Current, defaultSeriesVersion) resetJujuXDGDataHome(c) s.mockBlockClient.numRetries = 0 s.mockBlockClient.retryCount = 0 s.mockBlockClient.loginError = errors.NewUnauthorized(nil, "") _, err := coretesting.RunCommand(c, s.newBootstrapCommand(), "devcontroller", "dummy", "--auto-upgrade") c.Check(err, jc.Satisfies, errors.IsUnauthorized) c.Check(s.mockBlockClient.retryCount, gc.Equals, 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, ) }
// httpContext provides context for HTTP handlers. type httpContext struct { // strictValidation means that empty modelUUID values are not valid. strictValidation bool // controllerModelOnly only validates the controller model. controllerModelOnly bool // srv holds the API server instance. srv *Server } type errorSender interface { sendError(w http.ResponseWriter, code int, err error) } var errUnauthorized = errors.NewUnauthorized(nil, "unauthorized") // stateForRequestUnauthenticated returns a state instance appropriate for // using for the model implicit in the given request // without checking any authentication information. func (ctxt *httpContext) stateForRequestUnauthenticated(r *http.Request) (*state.State, error) { modelUUID, err := validateModelUUID(validateArgs{ statePool: ctxt.srv.statePool, modelUUID: r.URL.Query().Get(":modeluuid"), strict: ctxt.strictValidation, controllerModelOnly: ctxt.controllerModelOnly, }) if err != nil { return nil, errors.Trace(err) } st, err := ctxt.srv.statePool.Get(modelUUID)
// 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, }, ) }