1use modkit_security::SecurityContext;
27
28#[derive(Debug)]
38pub struct Secured<'a, C> {
39 client: &'a C,
40 ctx: &'a SecurityContext,
41}
42
43impl<'a, C> Secured<'a, C> {
44 #[must_use]
57 pub fn new(client: &'a C, ctx: &'a SecurityContext) -> Self {
58 Self { client, ctx }
59 }
60
61 #[must_use]
69 pub fn client(&self) -> &'a C {
70 self.client
71 }
72
73 #[must_use]
82 pub fn ctx(&self) -> &'a SecurityContext {
83 self.ctx
84 }
85
86 #[must_use]
103 pub fn query<S: crate::odata::Schema>(&self) -> crate::odata::QueryBuilder<S> {
104 crate::odata::QueryBuilder::new()
105 }
106}
107
108impl<C> Clone for Secured<'_, C> {
109 fn clone(&self) -> Self {
110 *self
111 }
112}
113
114impl<C> Copy for Secured<'_, C> {}
115
116pub trait WithSecurityContext {
129 fn security_ctx<'a>(&'a self, ctx: &'a SecurityContext) -> Secured<'a, Self>
146 where
147 Self: Sized;
148}
149
150impl<T> WithSecurityContext for T {
151 fn security_ctx<'a>(&'a self, ctx: &'a SecurityContext) -> Secured<'a, Self>
152 where
153 Self: Sized,
154 {
155 Secured::new(self, ctx)
156 }
157}
158
159#[cfg(test)]
160#[cfg_attr(coverage_nightly, coverage(off))]
161mod tests {
162 use super::*;
163 use uuid::{Uuid, uuid};
164
165 const TEST_TENANT_ID: Uuid = uuid!("00000000-0000-0000-0000-000000000001");
167 const TEST_SUBJECT_ID: Uuid = uuid!("00000000-0000-0000-0000-000000000002");
169
170 fn test_ctx() -> SecurityContext {
171 SecurityContext::builder()
172 .subject_id(TEST_SUBJECT_ID)
173 .subject_tenant_id(TEST_TENANT_ID)
174 .build()
175 .unwrap()
176 }
177
178 struct MockClient {
179 name: String,
180 }
181
182 impl MockClient {
183 fn new(name: &str) -> Self {
184 Self {
185 name: name.to_owned(),
186 }
187 }
188
189 fn get_name(&self) -> &str {
190 &self.name
191 }
192 }
193
194 #[test]
195 fn test_secured_new() {
196 let client = MockClient::new("test-client");
197 let ctx = test_ctx();
198
199 let secured = Secured::new(&client, &ctx);
200
201 assert_eq!(secured.client().get_name(), "test-client");
202 assert_eq!(secured.ctx().subject_tenant_id(), ctx.subject_tenant_id());
203 }
204
205 #[test]
206 fn test_secured_getters() {
207 let client = MockClient::new("test-client");
208 let ctx = test_ctx();
209
210 let secured = Secured::new(&client, &ctx);
211
212 let client_ref = secured.client();
213 assert_eq!(client_ref.get_name(), "test-client");
214
215 let ctx_ref = secured.ctx();
216 assert_eq!(ctx_ref.subject_tenant_id(), TEST_TENANT_ID);
217 }
218
219 #[test]
220 fn test_with_security_context_trait() {
221 let client = MockClient::new("test-client");
222 let ctx = test_ctx();
223
224 let secured = client.security_ctx(&ctx);
225
226 assert_eq!(secured.client().get_name(), "test-client");
227 assert_eq!(secured.ctx().subject_tenant_id(), ctx.subject_tenant_id());
228 }
229
230 #[test]
231 fn test_secured_clone() {
232 let client = MockClient::new("test-client");
233 let ctx = test_ctx();
234
235 let secured1 = client.security_ctx(&ctx);
236 let secured2 = secured1;
237
238 assert_eq!(secured1.client().get_name(), secured2.client().get_name());
239 assert_eq!(
240 secured1.ctx().subject_tenant_id(),
241 secured2.ctx().subject_tenant_id()
242 );
243 }
244
245 #[test]
246 fn test_secured_copy() {
247 let client = MockClient::new("test-client");
248 let ctx = test_ctx();
249
250 let secured1 = client.security_ctx(&ctx);
251 let secured2 = secured1;
252
253 assert_eq!(secured1.client().get_name(), secured2.client().get_name());
254 assert_eq!(
255 secured1.ctx().subject_tenant_id(),
256 secured2.ctx().subject_tenant_id()
257 );
258 }
259
260 #[test]
261 fn test_secured_with_anonymous_context() {
262 let client = MockClient::new("test-client");
263 let ctx = SecurityContext::anonymous();
264
265 let secured = client.security_ctx(&ctx);
266
267 assert_eq!(secured.client().get_name(), "test-client");
268 assert_eq!(secured.ctx().subject_tenant_id(), Uuid::default());
269 assert_eq!(secured.ctx().subject_id(), Uuid::default());
270 }
271
272 #[test]
273 fn test_secured_with_custom_context() {
274 let client = MockClient::new("test-client");
275 let tenant_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
276 let subject_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440001").unwrap();
277
278 let ctx = SecurityContext::builder()
279 .subject_tenant_id(tenant_id)
280 .subject_id(subject_id)
281 .subject_type("user")
282 .build()
283 .unwrap();
284
285 let secured = client.security_ctx(&ctx);
286
287 assert_eq!(secured.ctx().subject_tenant_id(), tenant_id);
288 assert_eq!(secured.ctx().subject_id(), subject_id);
289 }
290
291 #[test]
292 fn test_secured_zero_allocation() {
293 let client = MockClient::new("test-client");
294 let ctx = test_ctx();
295
296 let secured = client.security_ctx(&ctx);
297
298 assert_eq!(
299 std::mem::size_of_val(&secured),
300 std::mem::size_of::<&MockClient>() + std::mem::size_of::<&SecurityContext>()
301 );
302 }
303
304 #[test]
305 fn test_multiple_clients_with_same_context() {
306 let client1 = MockClient::new("client-1");
307 let client2 = MockClient::new("client-2");
308 let ctx = test_ctx();
309
310 let secured1 = client1.security_ctx(&ctx);
311 let secured2 = client2.security_ctx(&ctx);
312
313 assert_eq!(secured1.client().get_name(), "client-1");
314 assert_eq!(secured2.client().get_name(), "client-2");
315 assert_eq!(
316 secured1.ctx().subject_tenant_id(),
317 secured2.ctx().subject_tenant_id()
318 );
319 }
320
321 #[test]
322 fn test_secured_preserves_lifetimes() {
323 let client = MockClient::new("test-client");
324 let ctx = test_ctx();
325
326 let secured = client.security_ctx(&ctx);
327
328 assert_eq!(secured.client().get_name(), "test-client");
329 assert_eq!(secured.ctx().subject_tenant_id(), ctx.subject_tenant_id());
330 }
331
332 #[test]
333 fn test_secured_query_builder() {
334 use crate::odata::Schema;
335
336 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
337 enum TestField {
338 #[allow(dead_code)]
339 Name,
340 }
341
342 struct TestSchema;
343
344 impl Schema for TestSchema {
345 type Field = TestField;
346
347 fn field_name(field: Self::Field) -> &'static str {
348 match field {
349 TestField::Name => "name",
350 }
351 }
352 }
353
354 let client = MockClient::new("test-client");
355 let ctx = test_ctx();
356
357 let secured = client.security_ctx(&ctx);
358 let query_builder = secured.query::<TestSchema>();
359
360 let query = query_builder.page_size(50).build();
361 assert_eq!(query.limit, Some(50));
362 }
363}