Skip to content

Commit

Permalink
authz: protect GET for Projects (and prepare for more) (#618)
Browse files Browse the repository at this point in the history
  • Loading branch information
davepacheco authored Jan 24, 2022
1 parent 2a3c355 commit 6c742ea
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 149 deletions.
5 changes: 5 additions & 0 deletions nexus/src/authz/api_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,11 @@ pub struct Project {
}

impl Project {
// TODO-cleanup see note on Organization::id above.
pub fn id(&self) -> Uuid {
self.project_id
}

/// Returns an authz resource representing any child of this Project (e.g.,
/// an Instance or Disk)
///
Expand Down
88 changes: 53 additions & 35 deletions nexus/src/db/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ impl DataStore {
///
/// This function does no authz checks because it is not possible to know
/// just by looking up an Organization's id what privileges are required.
pub async fn organization_lookup_id(
pub async fn organization_lookup_path(
&self,
name: &Name,
) -> LookupResult<authz::Organization> {
Expand All @@ -340,8 +340,6 @@ impl DataStore {
) -> LookupResult<(authz::Organization, Organization)> {
let (authz_org, db_org) =
self.organization_lookup_noauthz(name).await?;
// TODO-security See the note in authz::authorize(). This needs to
// return a 404, not a 403.
opctx.authorize(authz::Action::Read, &authz_org).await?;
Ok((authz_org, db_org))
}
Expand Down Expand Up @@ -475,7 +473,7 @@ impl DataStore {
) -> UpdateResult<Organization> {
use db::schema::organization::dsl;

let authz_org = self.organization_lookup_id(name).await?;
let authz_org = self.organization_lookup_path(name).await?;
opctx.authorize(authz::Action::Modify, &authz_org).await?;

diesel::update(dsl::organization)
Expand Down Expand Up @@ -528,29 +526,72 @@ impl DataStore {
})
}

/// Lookup a project by name.
pub async fn project_fetch(
/// Fetches a Project from the database and returns both the database row
/// and an authz::Project for doing authz checks
///
/// See [`DataStore::organization_lookup_noauthz()`] for intended use cases
/// and caveats.
// TODO-security See the note on organization_lookup_noauthz().
async fn project_lookup_noauthz(
&self,
organization_id: &Uuid,
name: &Name,
) -> LookupResult<Project> {
authz_org: &authz::Organization,
project_name: &Name,
) -> LookupResult<(authz::Project, Project)> {
use db::schema::project::dsl;
dsl::project
.filter(dsl::time_deleted.is_null())
.filter(dsl::organization_id.eq(*organization_id))
.filter(dsl::name.eq(name.clone()))
.filter(dsl::organization_id.eq(authz_org.id()))
.filter(dsl::name.eq(project_name.clone()))
.select(Project::as_select())
.first_async(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ResourceType::Project,
LookupType::ByName(name.as_str().to_owned()),
LookupType::ByName(project_name.as_str().to_owned()),
)
})
.map(|p| {
(
authz_org
.project(p.id(), LookupType::from(&project_name.0)),
p,
)
})
}

/// Look up the id for a Project based on its name
///
/// Returns an [`authz::Project`] (which makes the id available).
///
/// This function does no authz checks because it is not possible to know
/// just by looking up an Project's id what privileges are required.
pub async fn project_lookup_path(
&self,
organization_name: &Name,
project_name: &Name,
) -> LookupResult<authz::Project> {
let authz_org =
self.organization_lookup_path(organization_name).await?;
self.project_lookup_noauthz(&authz_org, project_name)
.await
.map(|(p, _)| p)
}

/// Lookup a project by name.
pub async fn project_fetch(
&self,
opctx: &OpContext,
authz_org: &authz::Organization,
name: &Name,
) -> LookupResult<(authz::Project, Project)> {
let (authz_org, db_org) =
self.project_lookup_noauthz(authz_org, name).await?;
opctx.authorize(authz::Action::Read, &authz_org).await?;
Ok((authz_org, db_org))
}

/// Delete a project
/*
* TODO-correctness This needs to check whether there are any resources that
Expand Down Expand Up @@ -582,29 +623,6 @@ impl DataStore {
Ok(())
}

/// Look up the id for a project based on its name
pub async fn project_lookup_id_by_name(
&self,
organization_id: &Uuid,
name: &Name,
) -> Result<Uuid, Error> {
use db::schema::project::dsl;
dsl::project
.filter(dsl::time_deleted.is_null())
.filter(dsl::organization_id.eq(*organization_id))
.filter(dsl::name.eq(name.clone()))
.select(dsl::id)
.get_result_async::<Uuid>(self.pool())
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ResourceType::Project,
LookupType::ByName(name.as_str().to_owned()),
)
})
}

pub async fn projects_list_by_id(
&self,
opctx: &OpContext,
Expand Down
6 changes: 4 additions & 2 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,10 @@ async fn organization_projects_get_project(
let organization_name = &path.organization_name;
let project_name = &path.project_name;
let handler = async {
let project =
nexus.project_fetch(&organization_name, &project_name).await?;
let opctx = OpContext::for_external_api(&rqctx).await?;
let project = nexus
.project_fetch(&opctx, &organization_name, &project_name)
.await?;
Ok(HttpResponseOk(project.into()))
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
Expand Down
Loading

0 comments on commit 6c742ea

Please sign in to comment.