Skip to main content

ravenrs_app/security/
records.rs

1//! Users, roles and permissions as records, so administrators manage them on generated screens.
2
3use ravenrs_criteria::Value;
4
5use super::{Access, Operation, PermissionPolicy, Role};
6use crate::{EntityInfo, EnumOption, FieldInfo, FieldKind, ObjectSpace, Query, SpaceError};
7
8/// The entity of the people who can sign in.
9pub const SECURITY_USER: &str = "SecurityUser";
10/// The entity of roles.
11pub const SECURITY_ROLE: &str = "SecurityRole";
12/// The entity that says who has which role.
13pub const SECURITY_USER_ROLE: &str = "SecurityUserRole";
14/// What a role says about a whole entity.
15pub const SECURITY_TYPE_PERMISSION: &str = "SecurityTypePermission";
16/// What a role says about the records an expression holds for.
17pub const SECURITY_OBJECT_PERMISSION: &str = "SecurityObjectPermission";
18/// What a role says about fields.
19pub const SECURITY_MEMBER_PERMISSION: &str = "SecurityMemberPermission";
20/// What a role says about a navigation item.
21pub const SECURITY_NAVIGATION_PERMISSION: &str = "SecurityNavigationPermission";
22/// What a role says about an action.
23pub const SECURITY_ACTION_PERMISSION: &str = "SecurityActionPermission";
24/// The entity of the audit trail.
25pub const SECURITY_AUDIT_ENTRY: &str = "SecurityAuditEntry";
26/// The role the first person gets.
27pub const ADMINISTRATORS_ROLE: &str = "Administrators";
28
29const GROUP: &str = "Administration";
30
31pub(crate) fn is_security_entity(name: &str) -> bool {
32    [
33        SECURITY_USER,
34        SECURITY_ROLE,
35        SECURITY_USER_ROLE,
36        SECURITY_TYPE_PERMISSION,
37        SECURITY_OBJECT_PERMISSION,
38        SECURITY_MEMBER_PERMISSION,
39        SECURITY_NAVIGATION_PERMISSION,
40        SECURITY_ACTION_PERMISSION,
41    ]
42    .contains(&name)
43}
44
45fn key() -> FieldInfo {
46    FieldInfo::new("id", FieldKind::Guid).key()
47}
48
49fn access_kind() -> FieldKind {
50    FieldKind::Enum(vec![
51        EnumOption::new("", "Not set"),
52        EnumOption::new("allow", "Allow"),
53        EnumOption::new("deny", "Deny"),
54    ])
55}
56
57fn owned_list(name: &str, caption: &str, target: &str) -> FieldInfo {
58    FieldInfo::new(name, FieldKind::Collection(target.to_owned()))
59        .caption(caption)
60        .back_reference("role_id")
61        .aggregated()
62}
63
64fn of_role(name: &str) -> EntityInfo {
65    EntityInfo::new(name)
66        .field(key())
67        .field(FieldInfo::new("role_id", FieldKind::Reference(SECURITY_ROLE.to_owned())).caption("Role"))
68}
69
70/// The entities of users, roles and permissions, which storage has to know.
71pub fn security_entities() -> Vec<EntityInfo> {
72    let operations = |entity: EntityInfo| {
73        ["can_read", "can_write", "can_create", "can_delete"]
74            .iter()
75            .fold(entity, |entity, name| entity.field(FieldInfo::new(*name, access_kind())))
76    };
77    vec![
78        EntityInfo::new(SECURITY_USER)
79            .caption("Users")
80            .navigation(GROUP)
81            .icon("users")
82            .field(key())
83            .field(FieldInfo::new("name", FieldKind::Text).default_property())
84            .field(FieldInfo::new("active", FieldKind::Boolean).default_value(true))
85            .field(FieldInfo::new("must_change_password", FieldKind::Boolean).default_value(true))
86            .field(FieldInfo::new("password_hash", FieldKind::Text).secret())
87            .field(
88                FieldInfo::new("failed_attempts", FieldKind::Integer)
89                    .default_value(0.0)
90                    .read_only()
91                    .in_list(false)
92                    .in_detail(false),
93            )
94            .field(FieldInfo::new("locked_until", FieldKind::DateTime).read_only().in_list(false))
95            .field(
96                FieldInfo::new("roles", FieldKind::Collection(SECURITY_USER_ROLE.to_owned()))
97                    .back_reference("user_id"),
98            )
99            .rule(crate::Rule::required("name"))
100            .rule(crate::Rule::unique("name")),
101        EntityInfo::new(SECURITY_USER_ROLE)
102            .caption("Roles of a user")
103            .field(key())
104            .field(FieldInfo::new("user_id", FieldKind::Reference(SECURITY_USER.to_owned())).caption("User"))
105            .field(FieldInfo::new("role_id", FieldKind::Reference(SECURITY_ROLE.to_owned())).caption("Role")),
106        EntityInfo::new(SECURITY_ROLE)
107            .caption("Roles")
108            .navigation(GROUP)
109            .icon("shield")
110            .field(key())
111            .field(FieldInfo::new("name", FieldKind::Text).default_property())
112            .field(FieldInfo::new(
113                "policy",
114                FieldKind::Enum(vec![
115                    EnumOption::new("deny_all", "Deny all"),
116                    EnumOption::new("read_only_all", "Read only"),
117                    EnumOption::new("allow_all", "Allow all"),
118                ]),
119            ))
120            .field(FieldInfo::new("administrative", FieldKind::Boolean).default_value(false))
121            .field(FieldInfo::new("edit_model", FieldKind::Boolean).default_value(false))
122            .field(owned_list("type_permissions", "Entities", SECURITY_TYPE_PERMISSION))
123            .field(owned_list("object_permissions", "Records", SECURITY_OBJECT_PERMISSION))
124            .field(owned_list("member_permissions", "Fields", SECURITY_MEMBER_PERMISSION))
125            .field(owned_list("navigation_permissions", "Navigation", SECURITY_NAVIGATION_PERMISSION))
126            .field(owned_list("action_permissions", "Actions", SECURITY_ACTION_PERMISSION))
127            .rule(crate::Rule::required("name"))
128            .rule(crate::Rule::unique("name")),
129        operations(
130            of_role(SECURITY_TYPE_PERMISSION)
131                .caption("Permissions on entities")
132                .field(FieldInfo::new("target_entity", FieldKind::Text).default_property()),
133        ),
134        operations(
135            of_role(SECURITY_OBJECT_PERMISSION)
136                .caption("Permissions on records")
137                .field(FieldInfo::new("target_entity", FieldKind::Text).default_property())
138                .field(FieldInfo::new("criteria", FieldKind::Criteria)),
139        ),
140        of_role(SECURITY_MEMBER_PERMISSION)
141            .caption("Permissions on fields")
142            .field(FieldInfo::new("target_entity", FieldKind::Text).default_property())
143            .field(FieldInfo::new("fields", FieldKind::Text).caption("Fields, separated by commas"))
144            .field(FieldInfo::new("criteria", FieldKind::Criteria))
145            .field(FieldInfo::new("can_read", access_kind()))
146            .field(FieldInfo::new("can_write", access_kind())),
147        of_role(SECURITY_NAVIGATION_PERMISSION)
148            .caption("Permissions on navigation")
149            .field(FieldInfo::new("item", FieldKind::Text).default_property())
150            .field(FieldInfo::new("access", access_kind())),
151        of_role(SECURITY_ACTION_PERMISSION)
152            .caption("Permissions on actions")
153            .field(FieldInfo::new("action", FieldKind::Text).default_property())
154            .field(FieldInfo::new("access", access_kind())),
155    ]
156}
157
158/// The entity of the audit trail, which storage has to know when there is one.
159pub fn audit_entity() -> EntityInfo {
160    EntityInfo::new(SECURITY_AUDIT_ENTRY)
161        .caption("Audit trail")
162        .navigation(GROUP)
163        .icon("history")
164        .field(key())
165        .field(FieldInfo::new("happened_at", FieldKind::DateTime).read_only())
166        .field(FieldInfo::new("author", FieldKind::Text).read_only())
167        .field(FieldInfo::new(
168            "change",
169            FieldKind::Enum(vec![
170                EnumOption::new("created", "Created"),
171                EnumOption::new("initial_value", "Initial value"),
172                EnumOption::new("changed", "Changed"),
173                EnumOption::new("deleted", "Deleted"),
174                EnumOption::new("added_to_collection", "Added to a collection"),
175                EnumOption::new("removed_from_collection", "Removed from a collection"),
176                EnumOption::new("collection_object_changed", "Changed in a collection"),
177                EnumOption::new("custom", "Custom"),
178            ]),
179        ))
180        .field(FieldInfo::new("target_entity", FieldKind::Text).read_only().default_property())
181        .field(FieldInfo::new("target_key", FieldKind::Text).read_only())
182        .field(FieldInfo::new("field", FieldKind::Text).read_only())
183        .field(FieldInfo::new("old_value", FieldKind::LongText).read_only())
184        .field(FieldInfo::new("new_value", FieldKind::LongText).read_only())
185}
186
187fn access_of(value: &Value) -> Option<Access> {
188    match value {
189        Value::Text(text) if text == "allow" => Some(Access::Allow),
190        Value::Text(text) if text == "deny" => Some(Access::Deny),
191        _ => None,
192    }
193}
194
195fn text_of(value: &Value) -> String {
196    match value {
197        Value::Text(text) => text.clone(),
198        _ => String::new(),
199    }
200}
201
202/// The rows of a table that belong to this role.
203fn rows_of(space: &dyn ObjectSpace, entity: &str, role: &Value) -> Result<Vec<crate::Object>, SpaceError> {
204    Ok(space.query(entity, &Query::all())?.into_iter().filter(|row| row.get("role_id") == *role).collect())
205}
206
207/// A role as its records say, with everything it has.
208pub(crate) fn role_from_records(space: &dyn ObjectSpace, record: &crate::Object) -> Result<Role, SpaceError> {
209    let key = record.get("id");
210    let mut role =
211        Role::new(text_of(&record.get("name"))).policy(match text_of(&record.get("policy")).as_str() {
212            "read_only_all" => PermissionPolicy::ReadOnlyAll,
213            "allow_all" => PermissionPolicy::AllowAll,
214            _ => PermissionPolicy::DenyAll,
215        });
216    role.administrative = record.get("administrative") == Value::Bool(true);
217    role.edit_model = role.administrative || record.get("edit_model") == Value::Bool(true);
218    let operations = [
219        ("can_read", Operation::Read),
220        ("can_write", Operation::Write),
221        ("can_create", Operation::Create),
222        ("can_delete", Operation::Delete),
223    ];
224    for row in rows_of(space, SECURITY_TYPE_PERMISSION, &key)? {
225        let entity = text_of(&row.get("target_entity"));
226        for (name, operation) in operations {
227            if let Some(access) = access_of(&row.get(name)) {
228                role = role.type_permission(entity.clone(), [operation], access);
229            }
230        }
231    }
232    for row in rows_of(space, SECURITY_OBJECT_PERMISSION, &key)? {
233        let (entity, criteria) = (text_of(&row.get("target_entity")), text_of(&row.get("criteria")));
234        for (name, operation) in operations {
235            if let Some(access) = access_of(&row.get(name)) {
236                role = role.object_permission(entity.clone(), &criteria, [operation], access);
237            }
238        }
239    }
240    for row in rows_of(space, SECURITY_MEMBER_PERMISSION, &key)? {
241        let entity = text_of(&row.get("target_entity"));
242        let (fields, criteria) = (text_of(&row.get("fields")), text_of(&row.get("criteria")));
243        let names: Vec<String> =
244            fields.split(',').map(str::trim).filter(|name| !name.is_empty()).map(str::to_owned).collect();
245        let condition = (!criteria.trim().is_empty()).then_some(criteria.as_str());
246        for (name, operation) in &operations[..2] {
247            if let Some(access) = access_of(&row.get(name)) {
248                role = role.member_permission(entity.clone(), names.clone(), condition, [*operation], access);
249            }
250        }
251    }
252    for row in rows_of(space, SECURITY_NAVIGATION_PERMISSION, &key)? {
253        if let Some(access) = access_of(&row.get("access")) {
254            role = role.navigation_permission(text_of(&row.get("item")), access);
255        }
256    }
257    for row in rows_of(space, SECURITY_ACTION_PERMISSION, &key)? {
258        if let Some(access) = access_of(&row.get("access")) {
259            role = role.action_permission(text_of(&row.get("action")), access);
260        }
261    }
262    Ok(role)
263}