import export rbac
This commit is contained in:
2
server
2
server
Submodule server updated: 4759f1308d...9ad620a72f
Submodule tui-canvas updated: f9d2a7a312...7c3775469e
@@ -109,6 +109,7 @@ admin-add-table = Přidat tabulku
|
|||||||
admin-add-logic = Přidat logiku
|
admin-add-logic = Přidat logiku
|
||||||
admin-add-validation = Přidat validaci
|
admin-add-validation = Přidat validaci
|
||||||
admin-add-rule = Přidat pravidlo
|
admin-add-rule = Přidat pravidlo
|
||||||
|
admin-import = Import
|
||||||
admin-export = Export
|
admin-export = Export
|
||||||
admin-exchange-rates = Devizové kurzy
|
admin-exchange-rates = Devizové kurzy
|
||||||
admin-unavailable-title = Administrační panel není dostupný
|
admin-unavailable-title = Administrační panel není dostupný
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ admin-add-table = Add table
|
|||||||
admin-add-logic = Add logic
|
admin-add-logic = Add logic
|
||||||
admin-add-validation = Add validation
|
admin-add-validation = Add validation
|
||||||
admin-add-rule = Add rule
|
admin-add-rule = Add rule
|
||||||
|
admin-import = Import
|
||||||
admin-export = Export
|
admin-export = Export
|
||||||
admin-exchange-rates = Exchange rates
|
admin-exchange-rates = Exchange rates
|
||||||
admin-unavailable-title = Admin panel unavailable
|
admin-unavailable-title = Admin panel unavailable
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ admin-add-table = Pridať tabuľku
|
|||||||
admin-add-logic = Pridať logiku
|
admin-add-logic = Pridať logiku
|
||||||
admin-add-validation = Pridať validáciu
|
admin-add-validation = Pridať validáciu
|
||||||
admin-add-rule = Pridať pravidlo
|
admin-add-rule = Pridať pravidlo
|
||||||
|
admin-import = Import
|
||||||
admin-export = Export
|
admin-export = Export
|
||||||
admin-exchange-rates = Devízové kurzy
|
admin-exchange-rates = Devízové kurzy
|
||||||
admin-unavailable-title = Administračný panel nie je dostupný
|
admin-unavailable-title = Administračný panel nie je dostupný
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ pub(crate) const READ: &str = "read";
|
|||||||
/// per-profile question.
|
/// per-profile question.
|
||||||
pub(crate) const ALL_ECB: &str = "ecb:*";
|
pub(crate) const ALL_ECB: &str = "ecb:*";
|
||||||
|
|
||||||
|
/// Mirrors `server/src/auth/rbac/roles.rs`.
|
||||||
|
pub(crate) const SUPERADMIN: &str = "superadmin";
|
||||||
|
|
||||||
pub(crate) fn permits(snapshot: &AuthorizationSnapshot, object: &str, action: &str) -> bool {
|
pub(crate) fn permits(snapshot: &AuthorizationSnapshot, object: &str, action: &str) -> bool {
|
||||||
permissions_permit(&snapshot.permissions, object, action)
|
permissions_permit(&snapshot.permissions, object, action)
|
||||||
}
|
}
|
||||||
@@ -38,6 +41,14 @@ pub(crate) fn can_read_ecb(snapshot: &AuthorizationSnapshot) -> bool {
|
|||||||
permits(snapshot, ALL_ECB, READ)
|
permits(snapshot, ALL_ECB, READ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the caller is the superadmin. Bulk data transfer — CSV import and
|
||||||
|
/// export — is theirs alone: it moves whole tables at once, which is the same
|
||||||
|
/// reach as the backup they already hold, and a data role's own grants say
|
||||||
|
/// nothing about whether it should have that.
|
||||||
|
pub(crate) fn is_superadmin(snapshot: &AuthorizationSnapshot) -> bool {
|
||||||
|
snapshot.role == SUPERADMIN
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn can_open_admin(snapshot: &AuthorizationSnapshot) -> bool {
|
pub(crate) fn can_open_admin(snapshot: &AuthorizationSnapshot) -> bool {
|
||||||
[
|
[
|
||||||
STRUCT_PROFILE,
|
STRUCT_PROFILE,
|
||||||
|
|||||||
@@ -175,9 +175,8 @@ pub(crate) async fn load_admin_page(
|
|||||||
can_manage_tables: crate::authz::can_manage(&authorization, crate::authz::STRUCT_TABLE),
|
can_manage_tables: crate::authz::can_manage(&authorization, crate::authz::STRUCT_TABLE),
|
||||||
can_manage_scripts: crate::authz::can_manage(&authorization, crate::authz::STRUCT_SCRIPT),
|
can_manage_scripts: crate::authz::can_manage(&authorization, crate::authz::STRUCT_SCRIPT),
|
||||||
can_manage_validations: crate::authz::can_manage(&authorization, crate::authz::STRUCT_VALIDATION),
|
can_manage_validations: crate::authz::can_manage(&authorization, crate::authz::STRUCT_VALIDATION),
|
||||||
can_export: authorization.permissions.iter().any(|permission| {
|
can_import: crate::authz::is_superadmin(&authorization),
|
||||||
permission.action == "read" && permission.object.starts_with("data:")
|
can_export: crate::authz::is_superadmin(&authorization),
|
||||||
}),
|
|
||||||
can_ecb: crate::authz::can_read_ecb(&authorization),
|
can_ecb: crate::authz::can_read_ecb(&authorization),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ pub(crate) struct AdminPageState {
|
|||||||
pub can_manage_tables: bool,
|
pub can_manage_tables: bool,
|
||||||
pub can_manage_scripts: bool,
|
pub can_manage_scripts: bool,
|
||||||
pub can_manage_validations: bool,
|
pub can_manage_validations: bool,
|
||||||
|
/// The two transfer pages are separate actions with separate permissions:
|
||||||
|
/// loading rows needs `insert`, taking them out needs `read`, and a role
|
||||||
|
/// with one of them is not offered the other.
|
||||||
|
pub can_import: bool,
|
||||||
pub can_export: bool,
|
pub can_export: bool,
|
||||||
/// Whether the exchange-rate pipeline is visible to this caller. Not a
|
/// Whether the exchange-rate pipeline is visible to this caller. Not a
|
||||||
/// structural area: it is granted through the ECB object, like the
|
/// structural area: it is granted through the ECB object, like the
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ mod tests {
|
|||||||
can_manage_tables: true,
|
can_manage_tables: true,
|
||||||
can_manage_scripts: true,
|
can_manage_scripts: true,
|
||||||
can_manage_validations: true,
|
can_manage_validations: true,
|
||||||
|
can_import: true,
|
||||||
can_export: true,
|
can_export: true,
|
||||||
can_ecb: true,
|
can_ecb: true,
|
||||||
};
|
};
|
||||||
@@ -81,6 +82,7 @@ mod tests {
|
|||||||
"/admin/logic/new",
|
"/admin/logic/new",
|
||||||
"/admin/validation/new",
|
"/admin/validation/new",
|
||||||
"/admin/validation/sets/new",
|
"/admin/validation/sets/new",
|
||||||
|
"/admin/import",
|
||||||
"/admin/export",
|
"/admin/export",
|
||||||
"/logout",
|
"/logout",
|
||||||
] {
|
] {
|
||||||
@@ -115,6 +117,7 @@ mod tests {
|
|||||||
can_manage_tables: true,
|
can_manage_tables: true,
|
||||||
can_manage_scripts: true,
|
can_manage_scripts: true,
|
||||||
can_manage_validations: true,
|
can_manage_validations: true,
|
||||||
|
can_import: true,
|
||||||
can_export: true,
|
can_export: true,
|
||||||
can_ecb: true,
|
can_ecb: true,
|
||||||
};
|
};
|
||||||
@@ -150,6 +153,7 @@ mod tests {
|
|||||||
can_manage_tables: true,
|
can_manage_tables: true,
|
||||||
can_manage_scripts: true,
|
can_manage_scripts: true,
|
||||||
can_manage_validations: true,
|
can_manage_validations: true,
|
||||||
|
can_import: true,
|
||||||
can_export: true,
|
can_export: true,
|
||||||
can_ecb: true,
|
can_ecb: true,
|
||||||
};
|
};
|
||||||
@@ -188,6 +192,7 @@ mod tests {
|
|||||||
can_manage_tables: true,
|
can_manage_tables: true,
|
||||||
can_manage_scripts: true,
|
can_manage_scripts: true,
|
||||||
can_manage_validations: true,
|
can_manage_validations: true,
|
||||||
|
can_import: true,
|
||||||
can_export: true,
|
can_export: true,
|
||||||
can_ecb: true,
|
can_ecb: true,
|
||||||
};
|
};
|
||||||
@@ -225,6 +230,7 @@ mod tests {
|
|||||||
can_manage_tables: true,
|
can_manage_tables: true,
|
||||||
can_manage_scripts: true,
|
can_manage_scripts: true,
|
||||||
can_manage_validations: true,
|
can_manage_validations: true,
|
||||||
|
can_import: true,
|
||||||
can_export: true,
|
can_export: true,
|
||||||
can_ecb: true,
|
can_ecb: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,10 +41,9 @@ pub(crate) async fn load_catalog(
|
|||||||
_ => LoadError::Backend(error.message().to_string()),
|
_ => LoadError::Backend(error.message().to_string()),
|
||||||
})?
|
})?
|
||||||
.into_inner();
|
.into_inner();
|
||||||
let has_required_permission = authorization.permissions.iter().any(|permission| {
|
// Hiding the link is not the check: the route is refused here too, so a
|
||||||
permission.action == required_action && permission.object.starts_with("data:")
|
// typed URL gets the same answer as a missing button.
|
||||||
});
|
if !crate::authz::is_superadmin(&authorization) {
|
||||||
if !has_required_permission {
|
|
||||||
return Err(LoadError::Forbidden);
|
return Err(LoadError::Forbidden);
|
||||||
}
|
}
|
||||||
let mut definitions = state.definitions;
|
let mut definitions = state.definitions;
|
||||||
|
|||||||
@@ -154,12 +154,10 @@ impl Nav {
|
|||||||
// panel: managing either roles or users is enough to open it.
|
// panel: managing either roles or users is enough to open it.
|
||||||
nav.can_permissions = crate::authz::can_manage(authorization, crate::authz::STRUCT_ROLE)
|
nav.can_permissions = crate::authz::can_manage(authorization, crate::authz::STRUCT_ROLE)
|
||||||
|| crate::authz::can_manage(authorization, crate::authz::STRUCT_USER);
|
|| crate::authz::can_manage(authorization, crate::authz::STRUCT_USER);
|
||||||
nav.can_import = authorization.permissions.iter().any(|permission| {
|
// Both transfer pages are the superadmin's — see
|
||||||
permission.action == "insert" && permission.object.starts_with("data:")
|
// `crate::authz::is_superadmin`.
|
||||||
});
|
nav.can_import = crate::authz::is_superadmin(authorization);
|
||||||
nav.can_export = authorization.permissions.iter().any(|permission| {
|
nav.can_export = nav.can_import;
|
||||||
permission.action == "read" && permission.object.starts_with("data:")
|
|
||||||
});
|
|
||||||
nav.can_ecb = crate::authz::can_read_ecb(authorization);
|
nav.can_ecb = crate::authz::can_read_ecb(authorization);
|
||||||
nav
|
nav
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
{% if page.can_manage_scripts %}<a href="/admin/logic/new">{{ nav.tr("admin-add-logic") }}</a>{% endif %}
|
{% if page.can_manage_scripts %}<a href="/admin/logic/new">{{ nav.tr("admin-add-logic") }}</a>{% endif %}
|
||||||
{% if page.can_manage_validations %}<a href="/admin/validation/new">{{ nav.tr("admin-add-validation") }}</a>{% endif %}
|
{% if page.can_manage_validations %}<a href="/admin/validation/new">{{ nav.tr("admin-add-validation") }}</a>{% endif %}
|
||||||
{% if page.can_manage_validations %}<a href="/admin/validation/sets/new">{{ nav.tr("admin-add-rule") }}</a>{% endif %}
|
{% if page.can_manage_validations %}<a href="/admin/validation/sets/new">{{ nav.tr("admin-add-rule") }}</a>{% endif %}
|
||||||
|
{% if page.can_import %}<a href="/admin/import">{{ nav.tr("admin-import") }}</a>{% endif %}
|
||||||
{% if page.can_export %}<a href="/admin/export">{{ nav.tr("admin-export") }}</a>{% endif %}
|
{% if page.can_export %}<a href="/admin/export">{{ nav.tr("admin-export") }}</a>{% endif %}
|
||||||
{% if page.can_ecb %}<a href="/admin/ecb">{{ nav.tr("admin-exchange-rates") }}</a>{% endif %}
|
{% if page.can_ecb %}<a href="/admin/ecb">{{ nav.tr("admin-exchange-rates") }}</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user