1use std::rc::Rc;
4
5use ravenrs_criteria::{Criteria, Value};
6
7use super::{Operation, SecurityContext};
8use crate::space::select;
9use crate::{
10 Change, EntityInfo, FieldInfo, Object, ObjectSpace, Query, SpaceError, SpaceEvent, SpaceWatcher,
11};
12
13#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
20pub struct SecuredObjectSpace {
21 inner: Box<dyn ObjectSpace>,
22 context: Rc<SecurityContext>,
23}
24
25impl std::fmt::Debug for SecuredObjectSpace {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.debug_struct("SecuredObjectSpace").field("context", &self.context).finish_non_exhaustive()
28 }
29}
30
31fn stored_fields(entity: &EntityInfo) -> impl Iterator<Item = &FieldInfo> {
33 entity.fields.iter().filter(|field| !field.kind.is_collection())
34}
35
36fn head(path: &str) -> &str {
38 path.split('.').next().unwrap_or(path).trim_start_matches('^')
39}
40
41fn reaches_out(criteria: &Criteria) -> bool {
44 let mut found = false;
45 criteria.walk(&mut |part| match part {
46 Criteria::Property(path) if path.len() > 1 => found = true,
47 Criteria::Aggregate(_) => found = true,
48 _ => {}
49 });
50 found
51}
52
53#[cfg_attr(feature = "live", ravenrs_live::expose)]
54impl SecuredObjectSpace {
55 pub fn new(inner: Box<dyn ObjectSpace>, context: Rc<SecurityContext>) -> Self {
57 Self { inner, context }
58 }
59
60 fn entity(&self, name: &str) -> Result<EntityInfo, SpaceError> {
61 self.context
62 .with(|permissions| permissions.entity(name).cloned())
63 .ok_or_else(|| SpaceError::UnknownEntity(name.to_owned()))
64 }
65
66 fn visible(&self, entity: &EntityInfo, mut record: Object) -> Option<Object> {
69 self.context.with(|permissions| {
70 let hidden: Vec<&str> = stored_fields(entity)
71 .filter(|field| !field.is_key)
72 .filter(|field| {
73 !permissions.can_field(Operation::Read, &entity.name, &field.name, Some(&record))
74 })
75 .map(|field| field.name.as_str())
76 .collect();
77 let whole = permissions.can(Operation::Read, &entity.name, Some(&record));
78 let some_field = stored_fields(entity).filter(|field| !field.is_key).count() > hidden.len();
80 if !whole && !some_field {
81 return None;
82 }
83 for name in hidden {
84 if record.values().contains_key(name) {
85 record.set(name, Value::Null);
86 }
87 }
88 Some(record)
89 })
90 }
91
92 fn storage_can_answer(&self, entity: &EntityInfo, query: &Query) -> bool {
95 self.context.with(|permissions| {
96 if permissions.depends_on_record(&entity.name)
97 || !permissions.can(Operation::Read, &entity.name, None)
98 {
99 return false;
100 }
101 let readable = |name: &str| {
102 entity.field_named(name).is_some_and(|field| !field.secret)
103 && permissions.can_field(Operation::Read, &entity.name, name, None)
104 };
105 let filter_fits = query.filter.as_ref().is_none_or(|filter| {
106 !reaches_out(filter) && filter.properties().iter().all(|path| readable(head(path)))
107 });
108 filter_fits && query.sort.iter().all(|(field, _)| readable(field))
109 })
110 }
111
112 fn check_new(&self, entity: &EntityInfo, object: &Object) -> Result<(), SpaceError> {
113 let fresh = entity.new_object();
114 self.context.with(|permissions| {
115 let name = &entity.name;
116 let mut object = object.clone();
117 entity.compute(&mut object, permissions.environment());
118 let object = &object;
119 if !permissions.can(Operation::Create, name, Some(object))
120 || !permissions.can(Operation::Write, name, Some(object))
121 {
122 return Err(self.context.denied());
123 }
124 for field in stored_fields(entity).filter(|field| !field.is_key && field.calculated.is_none()) {
125 let value = object.get(&field.name);
126 if value != fresh.get(&field.name)
127 && !permissions.can_field(Operation::Write, name, &field.name, Some(object))
128 {
129 return Err(self.context.denied());
130 }
131 }
132 Ok(())
133 })
134 }
135
136 fn over_stored(
140 &self,
141 entity: &EntityInfo,
142 mut object: Object,
143 stored: &Object,
144 ) -> Result<Option<Object>, SpaceError> {
145 self.context.with(|permissions| {
146 let name = &entity.name;
147 let mut changed = false;
148 let writable = || stored_fields(entity).filter(|field| field.calculated.is_none());
149 for field in writable() {
150 let (new, old) = (object.get(&field.name), stored.get(&field.name));
151 if new == old {
152 continue;
153 }
154 if new == Value::Null
155 && !permissions.can_field(Operation::Read, name, &field.name, Some(stored))
156 {
157 object.set(&field.name, old);
158 continue;
159 }
160 if !permissions.can_field(Operation::Write, name, &field.name, Some(stored)) {
161 return Err(self.context.denied());
162 }
163 changed = true;
164 }
165 if !changed {
166 return Ok(None);
167 }
168 let mut after = object.clone();
170 entity.compute(&mut after, permissions.environment());
171 for field in writable() {
172 if after.get(&field.name) != stored.get(&field.name)
173 && !permissions.can_field(Operation::Write, name, &field.name, Some(&after))
174 {
175 return Err(self.context.denied());
176 }
177 }
178 Ok(Some(object))
179 })
180 }
181}
182
183fn as_seen(context: &SecurityContext, change: &Change) -> Change {
186 let masked = |object: &Object| {
187 context.with(|permissions| {
188 let Some(entity) = permissions.entity(object.entity()) else { return object.clone() };
189 let mut seen = object.clone();
190 for field in stored_fields(entity).filter(|field| !field.is_key) {
191 if !permissions.can_field(Operation::Read, &entity.name, &field.name, Some(object)) {
192 seen.set(&field.name, Value::Null);
193 }
194 }
195 seen
196 })
197 };
198 match change {
199 Change::New(object) => Change::New(masked(object)),
200 Change::Updated(object) => Change::Updated(masked(object)),
201 Change::Deleted { entity, key } => Change::Deleted { entity: entity.clone(), key: key.clone() },
202 }
203}
204
205#[cfg_attr(feature = "live", ravenrs_live::expose(part = "objectspace"))]
206impl ObjectSpace for SecuredObjectSpace {
207 fn entities(&self) -> Vec<EntityInfo> {
208 self.inner.entities()
209 }
210
211 fn query(&self, entity: &str, query: &Query) -> Result<Vec<Object>, SpaceError> {
212 let info = self.entity(entity)?;
213 if self.storage_can_answer(&info, query) {
214 let found = self.inner.query(entity, query)?;
215 return Ok(found.into_iter().filter_map(|record| self.visible(&info, record)).collect());
216 }
217 crate::space::check_query(&info, query)?;
218 let everything = self.inner.query(entity, &Query::all())?;
222 let seen: Vec<Object> =
223 everything.into_iter().filter_map(|record| self.visible(&info, record)).collect();
224 self.context.with(|permissions| select(&info, seen, query, permissions.environment()))
225 }
226
227 fn find(&self, entity: &str, key: &Value) -> Result<Option<Object>, SpaceError> {
228 let info = self.entity(entity)?;
229 Ok(self.inner.find(entity, key)?.and_then(|record| self.visible(&info, record)))
230 }
231
232 fn new_object(&mut self, entity: &str) -> Result<Object, SpaceError> {
233 let allowed = self.context.with(|permissions| permissions.can(Operation::Create, entity, None));
234 if !allowed {
235 return Err(self.context.denied());
236 }
237 self.inner.new_object(entity)
238 }
239
240 fn save(&mut self, object: Object) -> Result<(), SpaceError> {
241 let info = self.entity(object.entity())?;
242 let key = info.key_field().map(|field| object.get(&field.name)).unwrap_or(Value::Null);
243 match self.inner.find(&info.name, &key)? {
244 Some(stored) => match self.over_stored(&info, object, &stored)? {
245 Some(written) => self.inner.save(written),
246 None => Ok(()),
247 },
248 None => {
249 self.check_new(&info, &object)?;
250 self.inner.save(object)
251 }
252 }
253 }
254
255 fn insert(&mut self, object: Object) -> Result<(), SpaceError> {
256 let info = self.entity(object.entity())?;
257 self.check_new(&info, &object)?;
258 self.inner.insert(object)
259 }
260
261 fn delete(&mut self, entity: &str, key: &Value) -> Result<(), SpaceError> {
262 let Some(stored) = self.inner.find(entity, key)? else { return self.inner.delete(entity, key) };
263 let allowed =
264 self.context.with(|permissions| permissions.can(Operation::Delete, entity, Some(&stored)));
265 if !allowed {
266 return Err(self.context.denied());
267 }
268 self.inner.delete(entity, key)
269 }
270
271 fn changes(&self) -> Vec<Change> {
272 let context = &self.context;
273 self.inner.changes().iter().map(|change| as_seen(context, change)).collect()
274 }
275
276 fn is_modified(&self) -> bool {
277 self.inner.is_modified()
278 }
279
280 fn commit(&mut self) -> Result<(), SpaceError> {
281 let touched = self.inner.changes().iter().any(|change| match change {
282 Change::New(object) | Change::Updated(object) => self.context.touches_security(object.entity()),
283 Change::Deleted { entity, .. } => self.context.touches_security(entity),
284 });
285 if touched && !self.context.directory().has_active_administrator(&*self.inner)? {
287 return Err(SpaceError::Refused(
288 "At least one active user has to keep an administrative role.".to_owned(),
289 ));
290 }
291 self.inner.commit()?;
292 if touched {
293 self.context.reload()?;
294 }
295 Ok(())
296 }
297
298 fn rollback(&mut self) {
299 self.inner.rollback();
300 }
301
302 fn refresh(&mut self) {
303 self.inner.refresh();
304 }
305
306 fn on_event(&mut self, mut watcher: SpaceWatcher) {
307 let context = self.context.clone();
309 self.inner.on_event(Box::new(move |event| match event {
310 SpaceEvent::Committing(changes) => {
311 let seen: Vec<Change> = changes.iter().map(|change| as_seen(&context, change)).collect();
312 watcher(SpaceEvent::Committing(&seen))
313 }
314 SpaceEvent::Committed(changes) => {
315 let seen: Vec<Change> = changes.iter().map(|change| as_seen(&context, change)).collect();
316 watcher(SpaceEvent::Committed(&seen))
317 }
318 other => watcher(other),
319 }));
320 }
321}