import export rbac
This commit is contained in:
@@ -109,6 +109,7 @@ admin-add-table = Přidat tabulku
|
||||
admin-add-logic = Přidat logiku
|
||||
admin-add-validation = Přidat validaci
|
||||
admin-add-rule = Přidat pravidlo
|
||||
admin-import = Import
|
||||
admin-export = Export
|
||||
admin-exchange-rates = Devizové kurzy
|
||||
admin-unavailable-title = Administrační panel není dostupný
|
||||
|
||||
@@ -114,6 +114,7 @@ admin-add-table = Add table
|
||||
admin-add-logic = Add logic
|
||||
admin-add-validation = Add validation
|
||||
admin-add-rule = Add rule
|
||||
admin-import = Import
|
||||
admin-export = Export
|
||||
admin-exchange-rates = Exchange rates
|
||||
admin-unavailable-title = Admin panel unavailable
|
||||
|
||||
@@ -109,6 +109,7 @@ admin-add-table = Pridať tabuľku
|
||||
admin-add-logic = Pridať logiku
|
||||
admin-add-validation = Pridať validáciu
|
||||
admin-add-rule = Pridať pravidlo
|
||||
admin-import = Import
|
||||
admin-export = Export
|
||||
admin-exchange-rates = Devízové kurzy
|
||||
admin-unavailable-title = Administračný panel nie je dostupný
|
||||
|
||||
@@ -14,6 +14,9 @@ pub(crate) const READ: &str = "read";
|
||||
/// per-profile question.
|
||||
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 {
|
||||
permissions_permit(&snapshot.permissions, object, action)
|
||||
}
|
||||
@@ -38,6 +41,14 @@ pub(crate) fn can_read_ecb(snapshot: &AuthorizationSnapshot) -> bool {
|
||||
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 {
|
||||
[
|
||||
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_scripts: crate::authz::can_manage(&authorization, crate::authz::STRUCT_SCRIPT),
|
||||
can_manage_validations: crate::authz::can_manage(&authorization, crate::authz::STRUCT_VALIDATION),
|
||||
can_export: authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "read" && permission.object.starts_with("data:")
|
||||
}),
|
||||
can_import: crate::authz::is_superadmin(&authorization),
|
||||
can_export: crate::authz::is_superadmin(&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_scripts: 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,
|
||||
/// Whether the exchange-rate pipeline is visible to this caller. Not a
|
||||
/// structural area: it is granted through the ECB object, like the
|
||||
|
||||
@@ -72,6 +72,7 @@ mod tests {
|
||||
can_manage_tables: true,
|
||||
can_manage_scripts: true,
|
||||
can_manage_validations: true,
|
||||
can_import: true,
|
||||
can_export: true,
|
||||
can_ecb: true,
|
||||
};
|
||||
@@ -81,6 +82,7 @@ mod tests {
|
||||
"/admin/logic/new",
|
||||
"/admin/validation/new",
|
||||
"/admin/validation/sets/new",
|
||||
"/admin/import",
|
||||
"/admin/export",
|
||||
"/logout",
|
||||
] {
|
||||
@@ -115,6 +117,7 @@ mod tests {
|
||||
can_manage_tables: true,
|
||||
can_manage_scripts: true,
|
||||
can_manage_validations: true,
|
||||
can_import: true,
|
||||
can_export: true,
|
||||
can_ecb: true,
|
||||
};
|
||||
@@ -150,6 +153,7 @@ mod tests {
|
||||
can_manage_tables: true,
|
||||
can_manage_scripts: true,
|
||||
can_manage_validations: true,
|
||||
can_import: true,
|
||||
can_export: true,
|
||||
can_ecb: true,
|
||||
};
|
||||
@@ -188,6 +192,7 @@ mod tests {
|
||||
can_manage_tables: true,
|
||||
can_manage_scripts: true,
|
||||
can_manage_validations: true,
|
||||
can_import: true,
|
||||
can_export: true,
|
||||
can_ecb: true,
|
||||
};
|
||||
@@ -225,6 +230,7 @@ mod tests {
|
||||
can_manage_tables: true,
|
||||
can_manage_scripts: true,
|
||||
can_manage_validations: true,
|
||||
can_import: true,
|
||||
can_export: true,
|
||||
can_ecb: true,
|
||||
};
|
||||
|
||||
@@ -41,10 +41,9 @@ pub(crate) async fn load_catalog(
|
||||
_ => LoadError::Backend(error.message().to_string()),
|
||||
})?
|
||||
.into_inner();
|
||||
let has_required_permission = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == required_action && permission.object.starts_with("data:")
|
||||
});
|
||||
if !has_required_permission {
|
||||
// Hiding the link is not the check: the route is refused here too, so a
|
||||
// typed URL gets the same answer as a missing button.
|
||||
if !crate::authz::is_superadmin(&authorization) {
|
||||
return Err(LoadError::Forbidden);
|
||||
}
|
||||
let mut definitions = state.definitions;
|
||||
|
||||
@@ -154,12 +154,10 @@ impl Nav {
|
||||
// panel: managing either roles or users is enough to open it.
|
||||
nav.can_permissions = crate::authz::can_manage(authorization, crate::authz::STRUCT_ROLE)
|
||||
|| crate::authz::can_manage(authorization, crate::authz::STRUCT_USER);
|
||||
nav.can_import = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "insert" && permission.object.starts_with("data:")
|
||||
});
|
||||
nav.can_export = authorization.permissions.iter().any(|permission| {
|
||||
permission.action == "read" && permission.object.starts_with("data:")
|
||||
});
|
||||
// Both transfer pages are the superadmin's — see
|
||||
// `crate::authz::is_superadmin`.
|
||||
nav.can_import = crate::authz::is_superadmin(authorization);
|
||||
nav.can_export = nav.can_import;
|
||||
nav.can_ecb = crate::authz::can_read_ecb(authorization);
|
||||
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_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_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_ecb %}<a href="/admin/ecb">{{ nav.tr("admin-exchange-rates") }}</a>{% endif %}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user