1use ravenrs_criteria::Value;
4
5use super::{Access, Operation, PermissionPolicy, Role};
6use crate::{EntityInfo, EnumOption, FieldInfo, FieldKind, ObjectSpace, Query, SpaceError};
7
8pub const SECURITY_USER: &str = "SecurityUser";
10pub const SECURITY_ROLE: &str = "SecurityRole";
12pub const SECURITY_USER_ROLE: &str = "SecurityUserRole";
14pub const SECURITY_TYPE_PERMISSION: &str = "SecurityTypePermission";
16pub const SECURITY_OBJECT_PERMISSION: &str = "SecurityObjectPermission";
18pub const SECURITY_MEMBER_PERMISSION: &str = "SecurityMemberPermission";
20pub const SECURITY_NAVIGATION_PERMISSION: &str = "SecurityNavigationPermission";
22pub const SECURITY_ACTION_PERMISSION: &str = "SecurityActionPermission";
24pub const SECURITY_AUDIT_ENTRY: &str = "SecurityAuditEntry";
26pub 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
70pub 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
158pub 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
202fn 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
207pub(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}