diff --git a/nexus/auth/src/authz/api_resources.rs b/nexus/auth/src/authz/api_resources.rs index e86423e0c65..7dfcadd3ec4 100644 --- a/nexus/auth/src/authz/api_resources.rs +++ b/nexus/auth/src/authz/api_resources.rs @@ -37,8 +37,6 @@ use super::{Authz, actor::AuthenticatedActor}; use crate::authn; use crate::context::OpContext; use authz_macros::authz_resource; -use futures::FutureExt; -use futures::future::BoxFuture; use nexus_db_fixed_data::FLEET_ID; use nexus_types::external_api::shared::{FleetRole, ProjectRole, SiloRole}; use omicron_common::api::external::{Error, LookupType, ResourceType}; @@ -46,8 +44,49 @@ use oso::PolarClass; use serde::{Deserialize, Serialize}; use uuid::Uuid; +/// An uninhabited type representing a resource with no parent. +/// +/// Used as the `Parent` associated type for root resources like `Fleet`. +/// Since it has no variants, code handling `NoParent` values is unreachable. +#[derive(Clone, Debug)] +pub enum NoParent {} + +impl oso::ToPolar for NoParent { + fn to_polar(self) -> oso::PolarValue { + match self {} + } +} + +impl AuthorizedResource for NoParent { + async fn load_roles( + &self, + _opctx: &OpContext, + _authn: &authn::Context, + _roleset: &mut RoleSet, + ) -> Result<(), Error> { + match *self {} + } + + fn on_unauthorized( + &self, + _authz: &Authz, + _error: Error, + _actor: AnyActor, + _action: Action, + ) -> Error { + match *self {} + } + + fn polar_class(&self) -> oso::Class { + match *self {} + } +} + /// Describes an authz resource that corresponds to an API resource that has a -/// corresponding ResourceType and is stored in the database +/// corresponding ResourceType and is stored in the database. +/// +/// This trait is object-safe and can be used with `dyn ApiResource`. For +/// resources that have a parent in the hierarchy, see [`ApiResourceWithParent`]. pub trait ApiResource: std::fmt::Debug + oso::ToPolar + Send + Sync + 'static { @@ -57,11 +96,6 @@ pub trait ApiResource: /// If roles cannot be assigned to this resource, returns `None`. fn as_resource_with_roles(&self) -> Option<&dyn ApiResourceWithRoles>; - /// If this resource has a parent in the API hierarchy whose assigned roles - /// can affect access to this resource, return the parent resource. - /// Otherwise, returns `None`. - fn parent(&self) -> Option<&dyn AuthorizedResource>; - fn resource_type(&self) -> ResourceType; fn lookup_type(&self) -> &LookupType; @@ -72,6 +106,21 @@ pub trait ApiResource: } } +/// Extension of [`ApiResource`] for resources that have a parent in the hierarchy. +/// +/// This trait adds the `Parent` associated type, which makes it non-object-safe. +/// The blanket impl of [`AuthorizedResource`] is provided for types implementing +/// this trait. +pub trait ApiResourceWithParent: ApiResource { + /// The parent resource type. Use `NoParent` for root resources. + type Parent: AuthorizedResource; + + /// If this resource has a parent in the API hierarchy whose assigned roles + /// can affect access to this resource, return the parent resource. + /// Otherwise, returns `None`. + fn parent(&self) -> Option<&Self::Parent>; +} + /// Describes an authz resource on which we allow users to assign roles pub trait ApiResourceWithRoles: ApiResource { fn resource_id(&self) -> Uuid; @@ -107,15 +156,15 @@ pub trait ApiResourceWithRolesType: ApiResourceWithRoles { impl AuthorizedResource for T where - T: ApiResource + oso::PolarClass + Clone, + T: ApiResourceWithParent + oso::PolarClass + Clone, { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> BoxFuture<'fut, Result<(), Error>> { - load_roles_for_resource_tree(self, opctx, authn, roleset).boxed() + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { + load_roles_for_resource_tree(self, opctx, authn, roleset).await } fn on_unauthorized( @@ -187,10 +236,6 @@ impl ApiResource for Fleet { Some(self) } - fn parent(&self) -> Option<&dyn AuthorizedResource> { - None - } - fn resource_type(&self) -> ResourceType { ResourceType::Fleet } @@ -205,6 +250,14 @@ impl ApiResource for Fleet { } } +impl ApiResourceWithParent for Fleet { + type Parent = NoParent; + + fn parent(&self) -> Option<&NoParent> { + None + } +} + impl ApiResourceWithRoles for Fleet { fn resource_id(&self) -> Uuid { *FLEET_ID @@ -258,14 +311,14 @@ impl PartialEq for QuiesceState { } impl AuthorizedResource for QuiesceState { - fn load_roles<'fut>( - &'fut self, - _: &'fut OpContext, - _: &'fut authn::Context, - _: &'fut mut RoleSet, - ) -> BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + _opctx: &OpContext, + _authn: &authn::Context, + _roleset: &mut RoleSet, + ) -> Result<(), Error> { // We don't use (database) roles to grant access to the quiesce state. - futures::future::ready(Ok(())).boxed() + Ok(()) } fn on_unauthorized( @@ -299,17 +352,17 @@ impl oso::PolarClass for BlueprintConfig { } impl AuthorizedResource for BlueprintConfig { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the BlueprintConfig, only permissions. But we // still need to load the Fleet-related roles to verify that the actor // has the "admin" role on the Fleet (possibly conferred from a Silo // role). - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -350,13 +403,13 @@ impl oso::PolarClass for ConsoleSessionList { } impl AuthorizedResource for ConsoleSessionList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -397,13 +450,13 @@ impl oso::PolarClass for DnsConfig { } impl AuthorizedResource for DnsConfig { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -444,16 +497,16 @@ impl oso::PolarClass for IpPoolList { } impl AuthorizedResource for IpPoolList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the IpPoolList, only permissions. But we still // need to load the Fleet-related roles to verify that the actor's role // on the Fleet (possibly conferred from a Silo role). - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -508,16 +561,16 @@ impl oso::PolarClass for MulticastGroupList { } impl AuthorizedResource for MulticastGroupList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the MulticastGroupList, only permissions. But we // still need to load the Fleet-related roles to verify that the actor's // role on the Fleet (possibly conferred from a Silo role). - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -566,16 +619,16 @@ impl oso::PolarClass for AuditLog { } impl AuthorizedResource for AuditLog { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the AuditLog, only permissions. But we still // need to load the Fleet-related roles to verify that the actor has the // viewer role on the Fleet (possibly conferred from a Silo role). - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -608,16 +661,16 @@ impl oso::PolarClass for DeviceAuthRequestList { } impl AuthorizedResource for DeviceAuthRequestList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the DeviceAuthRequestList, only permissions. But we // still need to load the Fleet-related roles to verify that the actor has the // "admin" role on the Fleet. - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -657,13 +710,13 @@ impl oso::PolarClass for Inventory { } impl AuthorizedResource for Inventory { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -707,15 +760,15 @@ impl oso::PolarClass for SiloCertificateList { } impl AuthorizedResource for SiloCertificateList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, authn, roleset) + self.silo().load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -759,15 +812,15 @@ impl oso::PolarClass for SiloIdentityProviderList { } impl AuthorizedResource for SiloIdentityProviderList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, authn, roleset) + self.silo().load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -808,15 +861,15 @@ impl oso::PolarClass for SiloUserList { } impl AuthorizedResource for SiloUserList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, authn, roleset) + self.silo().load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -857,15 +910,15 @@ impl oso::PolarClass for SiloGroupList { } impl AuthorizedResource for SiloGroupList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, authn, roleset) + self.silo().load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -915,14 +968,14 @@ impl oso::PolarClass for SiloUserSessionList { } impl AuthorizedResource for SiloUserSessionList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // To check for silo admin, we need to load roles from the parent silo. - self.silo_user().parent.load_roles(opctx, authn, roleset) + self.silo_user().parent.load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -970,14 +1023,14 @@ impl oso::PolarClass for SiloUserTokenList { } impl AuthorizedResource for SiloUserTokenList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // To check for silo admin, we need to load roles from the parent silo. - self.silo_user().parent.load_roles(opctx, authn, roleset) + self.silo_user().parent.load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -1018,15 +1071,15 @@ impl oso::PolarClass for VpcList { } impl AuthorizedResource for VpcList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on this resource, but we still need to load the // Project-related roles. - self.project().load_roles(opctx, authn, roleset) + self.project().load_roles(opctx, authn, roleset).await } fn on_unauthorized( @@ -1067,17 +1120,17 @@ impl oso::PolarClass for UpdateTrustRootList { } impl AuthorizedResource for UpdateTrustRootList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the UpdateTrustRootList, only permissions. // But we still need to load the Fleet-related roles to verify that the // actor has the "admin" role on the Fleet (possibly conferred from a // Silo role). - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -1110,17 +1163,17 @@ impl oso::PolarClass for TargetReleaseConfig { } impl AuthorizedResource for TargetReleaseConfig { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on the TargetReleaseConfig, only permissions. But we // still need to load the Fleet-related roles to verify that the actor // has the "admin" role on the Fleet (possibly conferred from a Silo // role). - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -1153,13 +1206,13 @@ impl oso::PolarClass for AlertClassList { } impl AuthorizedResource for AlertClassList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { - load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).boxed() + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { + load_roles_for_resource_tree(&FLEET, opctx, authn, roleset).await } fn on_unauthorized( @@ -1203,15 +1256,15 @@ impl oso::PolarClass for ScimClientBearerTokenList { } impl AuthorizedResource for ScimClientBearerTokenList { - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> Result<(), Error> { // There are no roles on this resource, but we still need to load the // Silo-related roles. - self.silo().load_roles(opctx, authn, roleset) + self.silo().load_roles(opctx, authn, roleset).await } fn on_unauthorized( diff --git a/nexus/auth/src/authz/context.rs b/nexus/auth/src/authz/context.rs index 20437b7f535..4d04879527b 100644 --- a/nexus/auth/src/authz/context.rs +++ b/nexus/auth/src/authz/context.rs @@ -11,13 +11,13 @@ use crate::authz::Action; use crate::authz::oso_generic; use crate::context::OpContext; use crate::storage::Storage; -use futures::future::BoxFuture; use omicron_common::api::external::Error; use omicron_common::bail_unless; use oso::Oso; use oso::OsoError; use slog::debug; use std::collections::BTreeSet; +use std::future::Future; use std::sync::Arc; /// Server-wide authorization context @@ -164,12 +164,12 @@ pub trait AuthorizedResource: oso::ToPolar + Send + Sync + 'static { /// That's how this works for most resources. There are other kinds of /// resources (like the Database itself) that aren't stored in the database /// and for which a different mechanism might be used. - fn load_roles<'fut>( - &'fut self, - opctx: &'fut OpContext, - authn: &'fut authn::Context, - roleset: &'fut mut RoleSet, - ) -> BoxFuture<'fut, Result<(), Error>>; + fn load_roles( + &self, + opctx: &OpContext, + authn: &authn::Context, + roleset: &mut RoleSet, + ) -> impl Future>; /// Invoked on authz failure to determine the final authz result /// @@ -252,13 +252,12 @@ mod test { #[derive(Clone, PolarClass)] struct UnregisteredResource; impl AuthorizedResource for UnregisteredResource { - fn load_roles<'fut>( - &'fut self, - _: &'fut OpContext, - _: &'fut authn::Context, - _: &'fut mut RoleSet, - ) -> futures::future::BoxFuture<'fut, Result<(), Error>> - { + async fn load_roles( + &self, + _: &OpContext, + _: &authn::Context, + _: &mut RoleSet, + ) -> Result<(), Error> { // authorize() shouldn't get far enough to call this. unimplemented!(); } diff --git a/nexus/auth/src/authz/oso_generic.rs b/nexus/auth/src/authz/oso_generic.rs index f2d0403eb55..be2dc6f3bc7 100644 --- a/nexus/auth/src/authz/oso_generic.rs +++ b/nexus/auth/src/authz/oso_generic.rs @@ -14,8 +14,6 @@ use crate::authn; use crate::context::OpContext; use anyhow::Context; use anyhow::ensure; -use futures::FutureExt; -use futures::future::BoxFuture; use omicron_common::api::external::Error; use oso::Oso; use oso::PolarClass; @@ -288,12 +286,12 @@ impl oso::PolarClass for Database { } impl AuthorizedResource for Database { - fn load_roles<'fut>( - &'fut self, - _: &'fut OpContext, - _: &'fut authn::Context, - _: &'fut mut RoleSet, - ) -> BoxFuture<'fut, Result<(), Error>> { + async fn load_roles( + &self, + _opctx: &OpContext, + _authn: &authn::Context, + _roleset: &mut RoleSet, + ) -> Result<(), Error> { // We don't use (database) roles to grant access to the database. The // role assignment is hardcoded for all authenticated users. See the // "has_role" Polar method above. @@ -303,7 +301,7 @@ impl AuthorizedResource for Database { // the type signature of roles supported by RoleSet. RoleSet is really // for roles on database objects -- it assumes they have a ResourceType // and id, neither of which is true for `Database`. - futures::future::ready(Ok(())).boxed() + Ok(()) } fn on_unauthorized( diff --git a/nexus/auth/src/authz/roles.rs b/nexus/auth/src/authz/roles.rs index c510d1d5917..818c2de7e63 100644 --- a/nexus/auth/src/authz/roles.rs +++ b/nexus/auth/src/authz/roles.rs @@ -34,7 +34,8 @@ //! request, and we don't want that thread to block while we hit the database. //! Both of these issues could be addressed with considerably more work. -use super::api_resources::ApiResource; +use super::api_resources::ApiResourceWithParent; +use super::context::AuthorizedResource; use crate::authn; use crate::context::OpContext; use omicron_common::api::external::Error; @@ -91,7 +92,7 @@ pub async fn load_roles_for_resource_tree( roleset: &mut RoleSet, ) -> Result<(), Error> where - R: ApiResource, + R: ApiResourceWithParent, { // If roles can be assigned directly on this resource, load them. if let Some(with_roles) = resource.as_resource_with_roles() { diff --git a/nexus/authz-macros/outputs/instance.txt b/nexus/authz-macros/outputs/instance.txt index 9b8af77c610..58fff45bbf3 100644 --- a/nexus/authz-macros/outputs/instance.txt +++ b/nexus/authz-macros/outputs/instance.txt @@ -64,9 +64,6 @@ impl oso::PolarClass for Instance { } } impl ApiResource for Instance { - fn parent(&self) -> Option<&dyn AuthorizedResource> { - Some(&self.parent) - } fn resource_type(&self) -> ResourceType { ResourceType::Instance } @@ -77,3 +74,9 @@ impl ApiResource for Instance { None } } +impl ApiResourceWithParent for Instance { + type Parent = Project; + fn parent(&self) -> Option<&Project> { + Some(&self.parent) + } +} diff --git a/nexus/authz-macros/outputs/organization.txt b/nexus/authz-macros/outputs/organization.txt index e6215a07aad..5933df8ad85 100644 --- a/nexus/authz-macros/outputs/organization.txt +++ b/nexus/authz-macros/outputs/organization.txt @@ -60,9 +60,6 @@ impl oso::PolarClass for Organization { } } impl ApiResource for Organization { - fn parent(&self) -> Option<&dyn AuthorizedResource> { - Some(&self.parent) - } fn resource_type(&self) -> ResourceType { ResourceType::Organization } @@ -73,3 +70,9 @@ impl ApiResource for Organization { None } } +impl ApiResourceWithParent for Organization { + type Parent = Fleet; + fn parent(&self) -> Option<&Fleet> { + Some(&self.parent) + } +} diff --git a/nexus/authz-macros/outputs/rack.txt b/nexus/authz-macros/outputs/rack.txt index 40826951ee6..bb4709b17a0 100644 --- a/nexus/authz-macros/outputs/rack.txt +++ b/nexus/authz-macros/outputs/rack.txt @@ -60,9 +60,6 @@ impl oso::PolarClass for Rack { } } impl ApiResource for Rack { - fn parent(&self) -> Option<&dyn AuthorizedResource> { - Some(&self.parent) - } fn resource_type(&self) -> ResourceType { ResourceType::Rack } @@ -73,3 +70,9 @@ impl ApiResource for Rack { None } } +impl ApiResourceWithParent for Rack { + type Parent = Fleet; + fn parent(&self) -> Option<&Fleet> { + Some(&self.parent) + } +} diff --git a/nexus/authz-macros/src/lib.rs b/nexus/authz-macros/src/lib.rs index d5533d7b287..a53ff638d22 100644 --- a/nexus/authz-macros/src/lib.rs +++ b/nexus/authz-macros/src/lib.rs @@ -606,10 +606,6 @@ fn do_authz_resource( } impl ApiResource for #resource_name { - fn parent(&self) -> Option<&dyn AuthorizedResource> { - Some(&self.parent) - } - fn resource_type(&self) -> ResourceType { ResourceType::#resource_name } @@ -625,6 +621,14 @@ fn do_authz_resource( } } + impl ApiResourceWithParent for #resource_name { + type Parent = #parent_resource_name; + + fn parent(&self) -> Option<&#parent_resource_name> { + Some(&self.parent) + } + } + #api_resource_roles_trait }) } diff --git a/nexus/db-queries/src/policy_test/coverage.rs b/nexus/db-queries/src/policy_test/coverage.rs index 08235332ff2..70ebc25b4cf 100644 --- a/nexus/db-queries/src/policy_test/coverage.rs +++ b/nexus/db-queries/src/policy_test/coverage.rs @@ -28,7 +28,7 @@ impl Coverage { /// Record that the Polar class associated with `covered` is covered by the /// test - pub fn covered(&mut self, covered: &dyn AuthorizedResource) { + pub fn covered(&mut self, covered: &R) { self.covered_class(covered.polar_class()) } diff --git a/nexus/db-queries/src/policy_test/resource_builder.rs b/nexus/db-queries/src/policy_test/resource_builder.rs index 2605ac32aa7..bdc7da172fd 100644 --- a/nexus/db-queries/src/policy_test/resource_builder.rs +++ b/nexus/db-queries/src/policy_test/resource_builder.rs @@ -8,10 +8,10 @@ use super::coverage::Coverage; use crate::db; use crate::db::datastore::SiloUserApiOnly; -use authz::ApiResource; use futures::FutureExt; use futures::future::BoxFuture; use nexus_auth::authz; +use nexus_auth::authz::ApiResource; use nexus_auth::authz::ApiResourceWithRolesType; use nexus_auth::authz::AuthorizedResource; use nexus_auth::context::OpContext; @@ -69,7 +69,10 @@ impl<'a> ResourceBuilder<'a> { /// Register a new resource for later testing, with no associated users or /// role assignments - pub fn new_resource(&mut self, resource: T) { + pub fn new_resource(&mut self, resource: T) + where + T: DynAuthorizedResource + AuthorizedResource, + { self.coverage.covered(&resource); self.resources.push(Arc::new(resource)); } @@ -79,8 +82,8 @@ impl<'a> ResourceBuilder<'a> { pub async fn new_resource_with_users(&mut self, resource: T) where T: DynAuthorizedResource - + ApiResourceWithRolesType + AuthorizedResource + + ApiResourceWithRolesType + Clone, T::AllowedRoles: IntoEnumIterator, { @@ -179,7 +182,7 @@ impl ResourceSet { /// all of them. (We could also change `authorize()` to be dynamically- /// dispatched. This would be a much more sprawling change. And it's not clear /// that our use case has much application outside of a test like this.) -pub trait DynAuthorizedResource: AuthorizedResource + std::fmt::Debug { +pub trait DynAuthorizedResource: std::fmt::Debug + Send + Sync { fn do_authorize<'a, 'b>( &'a self, opctx: &'b OpContext,