Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions nexus/src/app/iam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ impl super::Nexus {

// Silo users

pub async fn silo_users_list(
/// List users in the current Silo
pub async fn silo_users_list_current(
&self,
opctx: &OpContext,
pagparams: &DataPageParams<'_, Uuid>,
Expand All @@ -73,18 +74,38 @@ impl super::Nexus {
.await
}

pub async fn silo_user_fetch_by_id(
/// Fetch the currently-authenticated Silo user
pub async fn silo_user_fetch_self(
&self,
opctx: &OpContext,
silo_user_id: &Uuid,
) -> LookupResult<db::model::SiloUser> {
let &actor = opctx
.authn
.actor_required()
.internal_context("loading current user")?;
let (.., db_silo_user) = LookupPath::new(opctx, &self.db_datastore)
.silo_user_id(*silo_user_id)
.silo_user_id(actor.actor_id())
.fetch()
.await?;
Ok(db_silo_user)
}

pub async fn silo_users_list(
&self,
opctx: &OpContext,
silo_name: &Name,
pagparams: &DataPageParams<'_, Uuid>,
) -> ListResultVec<db::model::SiloUser> {
let (authz_silo, ..) = LookupPath::new(opctx, &self.db_datastore)
.silo_name(silo_name)
.fetch()
.await?;
let authz_silo_user_list = authz::SiloUserList::new(authz_silo);
self.db_datastore
.silo_users_list_by_id(opctx, &authz_silo_user_list, pagparams)
.await
}

// Built-in users

pub async fn users_builtin_list(
Expand Down
58 changes: 50 additions & 8 deletions nexus/src/app/silo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

//! Silos, Users, and SSH Keys.

use crate::authz::ApiResource;
use crate::context::OpContext;
use crate::db;
use crate::db::identity::{Asset, Resource};
Expand Down Expand Up @@ -141,15 +142,15 @@ impl super::Nexus {

// Users

pub async fn silo_fixed_user_create(
pub async fn silo_api_user_create(
&self,
opctx: &OpContext,
silo_name: &Name,
new_user_params: params::UserCreate,
) -> CreateResult<db::model::SiloUser> {
let datastore = self.datastore();
let authz_silo = opctx.authn.silo_required()?;
let (.., db_silo) = LookupPath::new(opctx, &datastore)
.silo_id(authz_silo.id())
let (authz_silo, db_silo) = LookupPath::new(opctx, &datastore)
.silo_name(silo_name)
.fetch()
.await?;

Expand All @@ -165,7 +166,7 @@ impl super::Nexus {
new_user_params.external_id.as_ref().to_owned(),
);
let authz_silo_user_list = authz::SiloUserList::new(authz_silo.clone());
//

// TODO-cleanup This authz check belongs in silo_user_create().
opctx
.authorize(authz::Action::CreateChild, &authz_silo_user_list)
Expand All @@ -175,16 +176,57 @@ impl super::Nexus {
Ok(db_silo_user)
}

pub async fn silo_api_user_delete(
&self,
opctx: &OpContext,
silo_name: &Name,
silo_user_id: Uuid,
) -> DeleteResult {
// Verify that this user is actually in this Silo.
let datastore = self.datastore();
let (authz_silo, db_silo) = LookupPath::new(opctx, datastore)
.silo_name(silo_name)
.fetch()
.await?;
let (_, authz_silo_user, db_silo_user) =
LookupPath::new(opctx, datastore)
.silo_user_id(silo_user_id)
.fetch_for(authz::Action::Delete)
.await?;
if db_silo_user.silo_id != authz_silo.id() {
return Err(authz_silo_user.not_found());
}

if db_silo.user_provision_type != UserProvisionType::ApiOnly {
return Err(Error::invalid_request(
"cannot delete users in this kind of Silo",
));
}

self.db_datastore.silo_user_delete(opctx, &authz_silo_user).await
}

pub async fn silo_user_fetch(
&self,
opctx: &OpContext,
silo_name: &Name,
silo_user_id: Uuid,
) -> LookupResult<db::model::SiloUser> {
let (.., db_silo_user) = LookupPath::new(opctx, &self.datastore())
.silo_user_id(silo_user_id)
let datastore = self.datastore();
let (authz_silo_requested, _) = LookupPath::new(opctx, datastore)
.silo_name(silo_name)
.fetch()
.await?;
Ok(db_silo_user)
let (_, authz_silo_user, db_silo_user) =
LookupPath::new(opctx, datastore)
.silo_user_id(silo_user_id)
.fetch()
.await?;
if db_silo_user.silo_id != authz_silo_requested.id() {
Err(authz_silo_user.not_found())
} else {
Ok(db_silo_user)
}
}

/// Based on an authenticated subject, fetch or create a silo user
Expand Down
3 changes: 3 additions & 0 deletions nexus/src/authz/omicron.polar
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ has_relation(silo: Silo, "parent_silo", user: SiloUser)
has_permission(actor: AuthenticatedActor, _perm: String, silo_user: SiloUser)
if actor.equals_silo_user(silo_user);

has_permission(actor: AuthenticatedActor, "read", silo_user: SiloUser)
if silo_user.silo in actor.silo;

resource SiloGroup {
permissions = [
"list_children",
Expand Down
73 changes: 73 additions & 0 deletions nexus/src/db/datastore/silo_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ use crate::db::model::SiloUser;
use crate::db::model::UserBuiltin;
use crate::db::pagination::paginated;
use crate::external_api::params;
use async_bb8_diesel::AsyncConnection;
use async_bb8_diesel::AsyncRunQueryDsl;
use chrono::Utc;
use diesel::prelude::*;
use nexus_types::identity::Asset;
use omicron_common::api::external::CreateResult;
use omicron_common::api::external::DataPageParams;
use omicron_common::api::external::DeleteResult;
use omicron_common::api::external::Error;
use omicron_common::api::external::ListResultVec;
use omicron_common::api::external::ResourceType;
Expand Down Expand Up @@ -64,6 +67,76 @@ impl DataStore {
})
}

/// Delete a Silo User
pub async fn silo_user_delete(
&self,
opctx: &OpContext,
authz_silo_user: &authz::SiloUser,
) -> DeleteResult {
opctx.authorize(authz::Action::Delete, authz_silo_user).await?;

// Delete the user and everything associated with them.
// TODO-scalability Many of these should be done in batches.
// TODO-robustness We might consider the RFD 192 "rcgen" pattern as well
// so that people can't, say, login while we do this.
self.pool_authorized(opctx)
.await?
.transaction_async(|mut conn| async move {
// Delete console sessions.
{
use db::schema::console_session::dsl;
diesel::delete(dsl::console_session)
.filter(dsl::silo_user_id.eq(authz_silo_user.id()))
.execute_async(&mut conn)
.await?;
}

// Delete device authentication tokens.
{
use db::schema::device_access_token::dsl;
diesel::delete(dsl::device_access_token)
.filter(dsl::silo_user_id.eq(authz_silo_user.id()))
.execute_async(&mut conn)
.await?;
}

// Delete group memberships.
{
use db::schema::silo_group_membership::dsl;
diesel::delete(dsl::silo_group_membership)
.filter(dsl::silo_user_id.eq(authz_silo_user.id()))
.execute_async(&mut conn)
.await?;
}

// Delete ssh keys.
{
use db::schema::ssh_key::dsl;
diesel::update(dsl::ssh_key)
.filter(dsl::silo_user_id.eq(authz_silo_user.id()))
.filter(dsl::time_deleted.is_null())
.set(dsl::time_deleted.eq(Utc::now()))
.execute_async(&mut conn)
.await?;
}

// Delete the user record.
// XXX-dap should use check_if_exists/execute_and_check
use db::schema::silo_user::dsl;
diesel::update(dsl::silo_user)
.filter(dsl::id.eq(authz_silo_user.id()))
.filter(dsl::time_deleted.is_null())
.set(dsl::time_deleted.eq(Utc::now()))
.execute_async(&mut conn)
//.check_if_exists::<SiloUser>(authz_silo_user.id())
//.execute_and_check(&mut conn)
.await?;
Ok(())
})
.await
.map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server))
}

/// Given an external ID, return
/// - Ok(Some((authz::SiloUser, SiloUser))) if that external id refers to an
/// existing silo user
Expand Down
8 changes: 1 addition & 7 deletions nexus/src/external_api/console_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ use hyper::Body;
use lazy_static::lazy_static;
use mime_guess;
use omicron_common::api::external::Error;
use omicron_common::api::external::InternalContext;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_urlencoded;
Expand Down Expand Up @@ -600,12 +599,7 @@ pub async fn session_me(
// as _somebody_. We could restrict this to session auth only, but it's
// not clear what the advantage would be.
let opctx = OpContext::for_external_api(&rqctx).await?;
let &actor = opctx
.authn
.actor_required()
.internal_context("loading current user")?;
let user =
nexus.silo_user_fetch_by_id(&opctx, &actor.actor_id()).await?;
let user = nexus.silo_user_fetch_self(&opctx).await?;
Ok(HttpResponseOk(user.into()))
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
Expand Down
Loading