224 lines
6.7 KiB
Rust
224 lines
6.7 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
|
|
use anyhow::Context as _;
|
|
use askama::Template;
|
|
use axum::{
|
|
extract::{Path, State},
|
|
response::{Html, IntoResponse as _, Redirect, Response},
|
|
};
|
|
use axum_extra::extract::Form;
|
|
use serde::Deserialize;
|
|
use sqlx::postgres::types::Oid;
|
|
use uuid::Uuid;
|
|
|
|
use crate::{
|
|
app_error::{not_found, AppError},
|
|
app_state::AppDbConn,
|
|
base_pooler::BasePooler,
|
|
bases::Base,
|
|
db_conns::init_role,
|
|
pg_acls::PgPrivilegeType,
|
|
pg_classes::{PgClass, PgRelKind},
|
|
pg_roles::{user_id_from_rolname, PgRole, RoleTree},
|
|
rel_invitations::RelInvitation,
|
|
settings::Settings,
|
|
users::{CurrentUser, User},
|
|
};
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct ListRelationsPagePath {
|
|
base_id: Uuid,
|
|
}
|
|
|
|
pub async fn list_relations_page(
|
|
State(settings): State<Settings>,
|
|
State(mut base_pooler): State<BasePooler>,
|
|
AppDbConn(mut app_db): AppDbConn,
|
|
CurrentUser(current_user): CurrentUser,
|
|
Path(ListRelationsPagePath { base_id }): Path<ListRelationsPagePath>,
|
|
) -> Result<Response, AppError> {
|
|
// FIXME auth
|
|
let base = Base::fetch_by_id(base_id, &mut *app_db)
|
|
.await?
|
|
.ok_or(AppError::NotFound("no base found with that id".to_owned()))?;
|
|
let mut client = base_pooler.acquire_for(base_id).await?;
|
|
let rolname = format!("{}{}", &base.user_role_prefix, current_user.id.simple());
|
|
init_role(&rolname, &mut client).await?;
|
|
|
|
let roles = PgRole::fetch_by_names_any(vec![rolname], &mut *client).await?;
|
|
let role = roles.first().context("role not found in pg_roles")?;
|
|
let granted_role_tree = RoleTree::fetch_granted(role.oid, &mut *client)
|
|
.await?
|
|
.context("unable to construct role tree")?;
|
|
let granted_roles: HashSet<String> = granted_role_tree
|
|
.flatten_inherited()
|
|
.into_iter()
|
|
.map(|role| role.rolname.clone())
|
|
.collect();
|
|
|
|
let all_rels = PgClass::fetch_all_by_kind_any([PgRelKind::OrdinaryTable], &mut *client).await?;
|
|
let accessible_rels: Vec<PgClass> = all_rels
|
|
.into_iter()
|
|
.filter(|rel| {
|
|
let privileges: HashSet<PgPrivilegeType> = rel
|
|
.relacl
|
|
.clone()
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.filter(|item| granted_roles.contains(&item.grantee))
|
|
.flat_map(|item| item.privileges)
|
|
.map(|privilege| privilege.privilege)
|
|
.collect();
|
|
privileges.contains(&PgPrivilegeType::Select)
|
|
})
|
|
.collect();
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "list_rels.html")]
|
|
struct ResponseTemplate {
|
|
base: Base,
|
|
rels: Vec<PgClass>,
|
|
settings: Settings,
|
|
}
|
|
|
|
Ok(Html(
|
|
ResponseTemplate {
|
|
base,
|
|
rels: accessible_rels,
|
|
settings,
|
|
}
|
|
.render()?,
|
|
)
|
|
.into_response())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct RelPagePath {
|
|
base_id: Uuid,
|
|
class_oid: u32,
|
|
}
|
|
|
|
pub async fn rel_index_page(
|
|
State(settings): State<Settings>,
|
|
AppDbConn(mut app_db): AppDbConn,
|
|
CurrentUser(current_user): CurrentUser,
|
|
Path(RelPagePath { base_id, class_oid }): Path<RelPagePath>,
|
|
) -> Result<Response, AppError> {
|
|
todo!();
|
|
}
|
|
|
|
pub async fn rel_rbac_page(
|
|
State(settings): State<Settings>,
|
|
State(mut base_pooler): State<BasePooler>,
|
|
AppDbConn(mut app_db): AppDbConn,
|
|
CurrentUser(current_user): CurrentUser,
|
|
Path(RelPagePath { base_id, class_oid }): Path<RelPagePath>,
|
|
) -> Result<Response, AppError> {
|
|
// FIXME: auth
|
|
let base = Base::fetch_by_id(base_id, &mut *app_db)
|
|
.await?
|
|
.ok_or(not_found!("no base found with id {}", base_id))?;
|
|
let mut client = base_pooler.acquire_for(base_id).await?;
|
|
let rolname = format!("{}{}", &base.user_role_prefix, current_user.id.simple());
|
|
init_role(&rolname, &mut client).await?;
|
|
let class = PgClass::fetch_by_oid(Oid(class_oid), &mut *client)
|
|
.await?
|
|
.ok_or(not_found!("no relation found with oid {}", class_oid))?;
|
|
let user_ids: Vec<Uuid> = class
|
|
.relacl
|
|
.clone()
|
|
.unwrap_or_default()
|
|
.iter()
|
|
.filter_map(|item| user_id_from_rolname(&item.grantee, &base.user_role_prefix).ok())
|
|
.collect();
|
|
let all_users = User::fetch_by_ids_any(user_ids, &mut *app_db).await?;
|
|
let interim_users: HashMap<String, User> = all_users
|
|
.into_iter()
|
|
.map(|user| {
|
|
(
|
|
format!("{}{}", base.user_role_prefix, user.id.simple()),
|
|
user,
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
let all_invites = RelInvitation::fetch_by_class_oid(Oid(class_oid), &mut *app_db).await?;
|
|
let mut invites_by_email: HashMap<String, Vec<RelInvitation>> = HashMap::new();
|
|
for invite in all_invites {
|
|
let entry = invites_by_email.entry(invite.email.clone()).or_default();
|
|
entry.push(invite);
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "rel_rbac.html")]
|
|
struct ResponseTemplate {
|
|
base: Base,
|
|
interim_users: HashMap<String, User>,
|
|
invites_by_email: HashMap<String, Vec<RelInvitation>>,
|
|
pg_class: PgClass,
|
|
settings: Settings,
|
|
}
|
|
|
|
Ok(Html(
|
|
ResponseTemplate {
|
|
base,
|
|
interim_users,
|
|
invites_by_email,
|
|
pg_class: class,
|
|
settings,
|
|
}
|
|
.render()?,
|
|
)
|
|
.into_response())
|
|
}
|
|
|
|
pub async fn rel_rbac_invite_page_get(
|
|
State(settings): State<Settings>,
|
|
) -> Result<Response, AppError> {
|
|
#[derive(Template)]
|
|
#[template(path = "rbac_invite.html")]
|
|
struct ResponseTemplate {
|
|
settings: Settings,
|
|
}
|
|
Ok(Html(ResponseTemplate { settings }.render()?).into_response())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct RbacInvitePagePostForm {
|
|
email: String,
|
|
}
|
|
|
|
pub async fn rel_rbac_invite_page_post(
|
|
State(settings): State<Settings>,
|
|
AppDbConn(mut app_db): AppDbConn,
|
|
CurrentUser(current_user): CurrentUser,
|
|
Path(RelPagePath { base_id, class_oid }): Path<RelPagePath>,
|
|
Form(form): Form<RbacInvitePagePostForm>,
|
|
) -> Result<Response, AppError> {
|
|
// FIXME auth
|
|
// FIXME form validation
|
|
for privilege in [
|
|
PgPrivilegeType::Select,
|
|
PgPrivilegeType::Insert,
|
|
PgPrivilegeType::Update,
|
|
PgPrivilegeType::Delete,
|
|
PgPrivilegeType::Truncate,
|
|
PgPrivilegeType::References,
|
|
PgPrivilegeType::Trigger,
|
|
] {
|
|
RelInvitation::upsertable()
|
|
.email(form.email.clone())
|
|
.base_id(base_id)
|
|
.class_oid(Oid(class_oid))
|
|
.privilege(privilege)
|
|
.created_by(current_user.id)
|
|
.build()?
|
|
.upsert(&mut *app_db)
|
|
.await?;
|
|
}
|
|
Ok(Redirect::to(&format!(
|
|
"{0}/d/{base_id}/r/{class_oid}/rbac",
|
|
settings.root_path
|
|
))
|
|
.into_response())
|
|
}
|