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 interim_models::{base::Base, rel_invitation::RelInvitation, user::User}; use interim_pgtypes::{ pg_acl::PgPrivilegeType, pg_class::{PgClass, PgRelKind}, pg_role::{PgRole, RoleTree, user_id_from_rolname}, }; use serde::Deserialize; use sqlx::postgres::types::Oid; use uuid::Uuid; use crate::{ app_error::AppError, app_state::AppDbConn, base_pooler::{self, BasePooler}, settings::Settings, user::CurrentUser, }; #[derive(Deserialize)] pub struct ListRelationsPagePath { base_id: Uuid, } pub async fn list_relations_page( State(settings): State, State(mut base_pooler): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(ListRelationsPagePath { base_id }): Path, ) -> Result { // FIXME auth let base = Base::with_id(base_id).fetch_one(&mut app_db).await?; let rolname = format!("{}{}", &base.user_role_prefix, current_user.id.simple()); let mut client = base_pooler .acquire_for(base_id, base_pooler::RoleAssignment::User(current_user.id)) .await?; let roles = PgRole::with_name_in(vec![rolname]) .fetch_all(&mut client) .await?; let role = roles.first().context("role not found in pg_roles")?; let granted_role_tree = RoleTree::granted_to(role.oid) .fetch_tree(&mut client) .await? .context("unable to construct role tree")?; let granted_roles: HashSet = granted_role_tree .flatten_inherited() .into_iter() .map(|role| role.rolname.clone()) .collect(); let all_rels = PgClass::with_kind_in([PgRelKind::OrdinaryTable]) .fetch_all(&mut client) .await?; let accessible_rels: Vec = all_rels .into_iter() .filter(|rel| { let privileges: HashSet = 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, 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, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(RelPagePath { base_id, class_oid }): Path, ) -> Result { todo!(); } pub async fn rel_rbac_page( State(settings): State, State(mut base_pooler): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(RelPagePath { base_id, class_oid }): Path, ) -> Result { // FIXME: auth let base = Base::with_id(base_id).fetch_one(&mut app_db).await?; let mut client = base_pooler .acquire_for(base_id, base_pooler::RoleAssignment::User(current_user.id)) .await?; let class = PgClass::with_oid(Oid(class_oid)) .fetch_one(&mut client) .await?; let user_ids: Vec = 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::with_id_in(user_ids).fetch_all(&mut app_db).await?; let interim_users: HashMap = all_users .into_iter() .map(|user| { ( format!("{}{}", base.user_role_prefix, user.id.simple()), user, ) }) .collect(); let all_invites = RelInvitation::belonging_to_rel(Oid(class_oid)) .fetch_all(&mut app_db) .await?; let mut invites_by_email: HashMap> = 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, invites_by_email: HashMap>, 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, ) -> Result { #[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, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(RelPagePath { base_id, class_oid }): Path, Form(form): Form, ) -> Result { // 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()) }