read table definition if I can import - web allows for import export to authorized users

This commit is contained in:
Priec
2026-08-17 16:43:15 +02:00
parent fa24cf0975
commit 6b676bc450
5 changed files with 35 additions and 11 deletions

2
server

Submodule server updated: b7504c894c...645b45e81b

View File

@@ -19,7 +19,8 @@ pub(crate) const ALL_ECB: &str = "ecb:*";
/// its own grant rather than something `read` implies, and loading one in is
/// its own grant rather than something `insert` implies — so a superadmin can
/// hand a role the row access without the bulk transfer, or the other way
/// round.
/// round. `import` is the whole authorization for the bulk endpoint: a role
/// that holds it may load a file without holding `insert`.
pub(crate) const EXPORT: &str = "export";
pub(crate) const IMPORT: &str = "import";
@@ -146,6 +147,25 @@ mod tests {
assert!(!outranks("sales", "clerk"));
}
/// The transfer links are shown on the grant and nothing else. A role
/// invented this morning holds them exactly as superadmin does, and a role
/// with every structural area but no transfer grant sees neither link.
#[test]
fn the_transfer_links_follow_the_grant_not_the_role_name() {
let warehouse = AuthorizationSnapshot {
role: "warehouse-night-shift".to_string(),
..snapshot(&[("data:acme/stock", IMPORT)])
};
assert!(can_transfer_anything(&warehouse, IMPORT));
assert!(!can_transfer_anything(&warehouse, EXPORT));
assert!(permits_table(&warehouse, "acme", "stock", IMPORT));
assert!(!permits_table(&warehouse, "acme", "invoice", IMPORT));
let structural = snapshot(&[(STRUCT_TABLE, MANAGE), (STRUCT_ROLE, MANAGE)]);
assert!(!can_transfer_anything(&structural, IMPORT));
assert!(!can_transfer_anything(&structural, EXPORT));
}
#[test]
fn data_wildcards_match_the_server_object_shapes() {
let global = snapshot(&[("data:*", "read")]);

View File

@@ -29,16 +29,18 @@ pub(crate) struct Profile {
/// The tables one transfer page may offer.
///
/// `transfer_action` is the page's own grant — `export` or `import`.
/// `row_action` is what the transfer does underneath, `read` or `insert`, and
/// it is checked as well: the backend authorizes the row calls the transfer
/// makes, so a table offered on the strength of the transfer grant alone would
/// be refused halfway through the download. A table appears here only when the
/// role holds both.
///
/// `row_action` is a second grant to require, for a page whose work the backend
/// authorizes as ordinary row calls: an export downloads by reading rows, so it
/// passes `read` and a table offered on the strength of `export` alone would be
/// refused halfway through the download. An import passes `None` — the bulk
/// endpoint authorizes `import` itself and never asks for `insert`, so
/// requiring one here would hide tables the server would happily accept.
pub(crate) async fn load_catalog(
state: AppState,
headers: &HeaderMap,
transfer_action: &str,
row_action: &str,
row_action: Option<&str>,
) -> Result<Catalog, LoadError> {
let request = authenticated_request(headers, GetAuthorizationRequest {})
.map_err(|_| LoadError::Unauthenticated)?;
@@ -58,7 +60,9 @@ pub(crate) async fn load_catalog(
}
let permits = |profile: &str, table: &str| {
crate::authz::permits_table(&authorization, profile, table, transfer_action)
&& crate::authz::permits_table(&authorization, profile, table, row_action)
&& row_action.is_none_or(|action| {
crate::authz::permits_table(&authorization, profile, table, action)
})
};
let mut definitions = state.definitions;
let tree = definitions

View File

@@ -8,7 +8,7 @@ pub(crate) async fn load_page(
state: AppState,
headers: &HeaderMap,
) -> Result<ExportPageState, LoadError> {
let catalog = load_catalog(state, headers, crate::authz::EXPORT, "read").await?;
let catalog = load_catalog(state, headers, crate::authz::EXPORT, Some("read")).await?;
Ok(ExportPageState {
nav: crate::ui::Nav::from_authorization(headers, "", &catalog.authorization),
catalog,

View File

@@ -13,7 +13,7 @@ pub(crate) async fn load_page(
form: ImportForm,
step: Step,
) -> Result<ImportPageState, LoadError> {
let catalog = load_catalog(state, headers, crate::authz::IMPORT, "insert").await?;
let catalog = load_catalog(state, headers, crate::authz::IMPORT, None).await?;
Ok(ImportPageState {
nav: crate::ui::Nav::from_authorization(headers, "", &catalog.authorization),
catalog,