web synchronized with the new changes
This commit is contained in:
@@ -13,7 +13,7 @@ use tonic::transport::Channel;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
auth::GetAuthorizationRequest,
|
||||
auth::{GetAuthorizationRequest, ListGrantableObjectsRequest, ListRolePermissionsRequest, ListRolesRequest},
|
||||
definitions::{
|
||||
common::Empty,
|
||||
table_definition::{
|
||||
@@ -27,7 +27,7 @@ use crate::{
|
||||
|
||||
use super::state::{
|
||||
DetailColumn, LoadError, PageInputs, RenameEntry, ScriptView, TableDefinitionPageState,
|
||||
TableDetailView, TableSummary,
|
||||
TableDetailView, TablePermissionAction, TableRolePermissions, TableSummary,
|
||||
};
|
||||
|
||||
/// Reads the column-type vocabulary on its own.
|
||||
@@ -67,7 +67,7 @@ pub(crate) async fn load_page(
|
||||
_ => LoadError::Backend(error.message().to_string()),
|
||||
})?
|
||||
.into_inner();
|
||||
if authorization.role != "admin" {
|
||||
if !crate::authz::can_manage(&authorization, crate::authz::STRUCT_TABLE) {
|
||||
return Err(LoadError::Forbidden);
|
||||
}
|
||||
|
||||
@@ -220,8 +220,83 @@ pub(crate) async fn load_page(
|
||||
false => Vec::new(),
|
||||
};
|
||||
|
||||
let mut permission_object = String::new();
|
||||
let mut role_permissions = Vec::new();
|
||||
if inputs.selection.has_table()
|
||||
&& crate::authz::can_manage(&authorization, crate::authz::STRUCT_ROLE)
|
||||
{
|
||||
let expected_object = crate::authz::table_object(
|
||||
&inputs.selection.profile,
|
||||
&inputs.selection.table,
|
||||
);
|
||||
let roles = auth
|
||||
.list_roles(
|
||||
authenticated_request(headers, ListRolesRequest {})
|
||||
.map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner()
|
||||
.roles;
|
||||
for role in roles.into_iter().filter(|role| role.kind == "data") {
|
||||
let grantable = auth
|
||||
.list_grantable_objects(
|
||||
authenticated_request(
|
||||
headers,
|
||||
ListGrantableObjectsRequest {
|
||||
target_role: role.name.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner()
|
||||
.objects
|
||||
.into_iter()
|
||||
.find(|object| object.object == expected_object);
|
||||
let Some(grantable) = grantable else {
|
||||
continue;
|
||||
};
|
||||
permission_object = expected_object.clone();
|
||||
let permissions = auth
|
||||
.list_role_permissions(
|
||||
authenticated_request(
|
||||
headers,
|
||||
ListRolePermissionsRequest {
|
||||
role: role.name.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(|_| LoadError::Unauthenticated)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadError::Backend(error.message().to_string()))?
|
||||
.into_inner();
|
||||
role_permissions.push(TableRolePermissions {
|
||||
role: role.name,
|
||||
actions: grantable
|
||||
.allowed_actions
|
||||
.into_iter()
|
||||
.map(|action| TablePermissionAction {
|
||||
direct: crate::authz::is_direct_permission(
|
||||
&permissions.permissions,
|
||||
&expected_object,
|
||||
&action,
|
||||
),
|
||||
effective: crate::authz::permissions_permit(
|
||||
&permissions.effective_permissions,
|
||||
&expected_object,
|
||||
&action,
|
||||
),
|
||||
action,
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TableDefinitionPageState {
|
||||
nav: crate::ui::Nav::new(headers, "admin").with_role(authorization.role),
|
||||
nav: crate::ui::Nav::new(headers, "admin").with_authorization(&authorization),
|
||||
profiles,
|
||||
tables,
|
||||
detail,
|
||||
@@ -235,5 +310,7 @@ pub(crate) async fn load_page(
|
||||
error: inputs.error,
|
||||
sql: inputs.sql,
|
||||
generated: inputs.generated,
|
||||
permission_object,
|
||||
role_permissions,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ fn load_error_response(error: LoadError) -> Response {
|
||||
LoadError::Forbidden => (
|
||||
StatusCode::FORBIDDEN,
|
||||
Html(ui::render_load_error(
|
||||
"Administrator access is required.",
|
||||
"Table-management permission is required.",
|
||||
)),
|
||||
)
|
||||
.into_response(),
|
||||
|
||||
@@ -255,6 +255,19 @@ pub(crate) struct TableDefinitionPageState {
|
||||
pub error: Option<String>,
|
||||
pub sql: Option<String>,
|
||||
pub generated: Vec<GeneratedTableView>,
|
||||
pub permission_object: String,
|
||||
pub role_permissions: Vec<TableRolePermissions>,
|
||||
}
|
||||
|
||||
pub(crate) struct TableRolePermissions {
|
||||
pub role: String,
|
||||
pub actions: Vec<TablePermissionAction>,
|
||||
}
|
||||
|
||||
pub(crate) struct TablePermissionAction {
|
||||
pub action: String,
|
||||
pub direct: bool,
|
||||
pub effective: bool,
|
||||
}
|
||||
|
||||
impl TableDefinitionPageState {
|
||||
|
||||
@@ -99,7 +99,7 @@ mod tests {
|
||||
use crate::{
|
||||
pages::admin::table_definition::state::{
|
||||
CopyForm, DetailColumn, InvoiceTemplateForm, RenameForm, Selection, TableDetailView,
|
||||
TableSummary,
|
||||
TablePermissionAction, TableRolePermissions, TableSummary,
|
||||
},
|
||||
schema::ColumnDraft,
|
||||
};
|
||||
@@ -149,6 +149,8 @@ mod tests {
|
||||
error: None,
|
||||
sql: None,
|
||||
generated: Vec::new(),
|
||||
permission_object: String::new(),
|
||||
role_permissions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +298,26 @@ mod tests {
|
||||
assert!(html.contains("1 column added"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_table_exposes_its_data_permission_actions() {
|
||||
let mut page = page();
|
||||
page.permission_object = "data:billing/invoice".to_string();
|
||||
page.role_permissions = vec![TableRolePermissions {
|
||||
role: "bookkeeper".to_string(),
|
||||
actions: vec![TablePermissionAction {
|
||||
action: "read".to_string(),
|
||||
direct: false,
|
||||
effective: false,
|
||||
}],
|
||||
}];
|
||||
|
||||
let html = render_workspace(&page);
|
||||
assert!(!html.contains("Template error"), "{html}");
|
||||
assert!(html.contains("Data permissions for"));
|
||||
assert!(html.contains("data:billing/invoice"));
|
||||
assert!(html.contains("Grant read"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_load_failure_answers_with_the_dialog() {
|
||||
let html = render_load_error("The backend is unreachable.");
|
||||
|
||||
Reference in New Issue
Block a user