1
#![allow(unused_variables)]
2

            
3
use std::borrow::Cow;
4

            
5
use crate::{
6
    Action, ActionName, Actionable, AsyncDispatcher, PermissionDenied, Permissions, ResourceName,
7
    Statement,
8
};
9

            
10
46
#[derive(Debug, Action)]
11
#[action(actionable = crate)]
12
enum TestActions {
13
    DoSomething,
14
    Post(PostActions),
15
}
16

            
17
17
#[derive(Debug, Action)]
18
#[action(actionable = crate)]
19
enum PostActions {
20
    Read,
21
    Update,
22
    Delete,
23
}
24

            
25
1
#[test]
26
1
fn basics() {
27
1
    // Default action is deny
28
1
    let statements = vec![
29
1
        // Allow Read on all.
30
1
        Statement::for_any().allowing(&TestActions::Post(PostActions::Read)),
31
1
        // Allow all actions for the resource named all-actions-allowed
32
1
        Statement::for_resource("all-actions-allowed").allowing_all(),
33
1
        // Allow all Post actions for the resource named only-post-actions-allowed
34
1
        Statement::for_resource("only-post-actions-allowed")
35
1
            .allowing(&ActionName(vec![Cow::Borrowed("Post")])),
36
1
    ];
37
1
    let permissions = Permissions::from(statements);
38
1

            
39
1
    // Check the positive cases:
40
1
    assert!(permissions.allowed_to(
41
1
        &ResourceName::named("someresource"),
42
1
        &TestActions::Post(PostActions::Read)
43
1
    ));
44
1
    assert!(permissions.allowed_to(
45
1
        &ResourceName::named("all-actions-allowed"),
46
1
        &TestActions::Post(PostActions::Update)
47
1
    ));
48
1
    assert!(permissions.allowed_to(
49
1
        &ResourceName::named("all-actions-allowed"),
50
1
        &TestActions::DoSomething
51
1
    ));
52
1
    assert!(permissions.allowed_to(
53
1
        &ResourceName::named("only-post-actions-allowed"),
54
1
        &TestActions::Post(PostActions::Delete)
55
1
    ));
56

            
57
    // Test the negatives
58
1
    assert!(!permissions.allowed_to(
59
1
        &ResourceName::named("someresource"),
60
1
        &TestActions::Post(PostActions::Update)
61
1
    ));
62
1
    assert!(!permissions.allowed_to(
63
1
        &ResourceName::named("someresource"),
64
1
        &TestActions::Post(PostActions::Delete)
65
1
    ));
66
1
    assert!(!permissions.allowed_to(
67
1
        &ResourceName::named("only-post-actions-allowed"),
68
1
        &TestActions::DoSomething
69
1
    ));
70
1
}
71

            
72
1
#[test]
73
1
fn multiple_actions() {
74
1
    // Default action is deny
75
1
    let statements = vec![
76
1
        // Allow Read on all.
77
1
        Statement::for_any()
78
1
            .allowing(&TestActions::Post(PostActions::Read))
79
1
            .allowing(&TestActions::Post(PostActions::Delete)),
80
1
    ];
81
1
    let permissions = Permissions::from(statements);
82
1

            
83
1
    // Check the positive cases:
84
1
    assert!(permissions.allowed_to(
85
1
        &ResourceName::named("someresource"),
86
1
        &TestActions::Post(PostActions::Read)
87
1
    ));
88
1
    assert!(permissions.allowed_to(
89
1
        &ResourceName::named("someresource"),
90
1
        &TestActions::Post(PostActions::Delete)
91
1
    ));
92

            
93
    // Check another permission
94
1
    assert!(!permissions.allowed_to(
95
1
        &ResourceName::named("someresource"),
96
1
        &TestActions::DoSomething
97
1
    ));
98
1
}
99

            
100
50
#[derive(Actionable, Debug)]
101
#[actionable(actionable = crate, async)]
102
enum Request {
103
    #[actionable(protection = "none")]
104
    UnprotectedNoParameters,
105
    #[actionable(protection = "none")]
106
    UnprotectedEnumParameter(u64),
107
    #[actionable(protection = "none")]
108
    UnprotectedStructParameter { value: u64 },
109

            
110
    #[actionable(protection = "simple")]
111
    SimplyProtectedNoParameters,
112
    #[actionable(protection = "simple")]
113
    SimplyProtectedEnumParameter(u64),
114
    #[actionable(protection = "simple")]
115
    SimplyProtectedStructParameter { value: u64 },
116

            
117
    #[actionable(protection = "custom")]
118
    CustomProtectedNoParameters,
119
    #[actionable(protection = "custom")]
120
    CustomProtectedEnumParameter(u64),
121
    #[actionable(protection = "custom")]
122
    CustomProtectedStructParameter { value: u64 },
123
}
124

            
125
28
#[derive(AsyncDispatcher, Debug)]
126
#[dispatcher(input = Request, actionable = crate)]
127
struct TestDispatcher;
128

            
129
#[async_trait::async_trait]
130
impl RequestDispatcher for TestDispatcher {
131
    type Error = TestError;
132
    type Output = Option<u64>;
133
}
134

            
135
#[derive(thiserror::Error, Debug)]
136
pub enum TestError {
137
    #[error("custom error")]
138
    CustomError,
139
    #[error("permission error: {0}")]
140
    PermissionDenied(#[from] PermissionDenied),
141
}
142

            
143
#[async_trait::async_trait]
144
impl UnprotectedEnumParameterHandler for TestDispatcher {
145
1
    async fn handle(
146
1
        &self,
147
1
        _permissions: &Permissions,
148
1
        arg1: u64,
149
1
    ) -> Result<Option<u64>, TestError> {
150
1
        Ok(Some(arg1))
151
1
    }
152
}
153

            
154
#[async_trait::async_trait]
155
impl UnprotectedStructParameterHandler for TestDispatcher {
156
1
    async fn handle(
157
1
        &self,
158
1
        _permissions: &Permissions,
159
1
        value: u64,
160
1
    ) -> Result<Option<u64>, TestError> {
161
1
        Ok(Some(value))
162
1
    }
163
}
164

            
165
#[async_trait::async_trait]
166
impl UnprotectedNoParametersHandler for TestDispatcher {
167
1
    async fn handle(&self, _permissions: &Permissions) -> Result<Option<u64>, TestError> {
168
1
        Ok(None)
169
1
    }
170
}
171

            
172
#[async_trait::async_trait]
173
impl SimplyProtectedEnumParameterHandler for TestDispatcher {
174
    type Action = TestActions;
175

            
176
2
    async fn resource_name<'a>(&'a self, arg1: &'a u64) -> Result<ResourceName<'a>, TestError> {
177
2
        Ok(ResourceName::named(*arg1))
178
2
    }
179

            
180
2
    fn action() -> Self::Action {
181
2
        TestActions::DoSomething
182
2
    }
183

            
184
1
    async fn handle_protected(
185
1
        &self,
186
1
        _permissions: &Permissions,
187
1
        arg1: u64,
188
1
    ) -> Result<Option<u64>, TestError> {
189
1
        Ok(Some(arg1))
190
1
    }
191
}
192

            
193
#[async_trait::async_trait]
194
impl SimplyProtectedStructParameterHandler for TestDispatcher {
195
    type Action = TestActions;
196

            
197
2
    async fn resource_name<'a>(&'a self, arg1: &'a u64) -> Result<ResourceName<'a>, TestError> {
198
2
        Ok(ResourceName::named(*arg1))
199
2
    }
200

            
201
2
    fn action() -> Self::Action {
202
2
        TestActions::DoSomething
203
2
    }
204

            
205
1
    async fn handle_protected(
206
1
        &self,
207
1
        _permissions: &Permissions,
208
1
        value: u64,
209
1
    ) -> Result<Option<u64>, TestError> {
210
1
        Ok(Some(value))
211
1
    }
212
}
213

            
214
#[async_trait::async_trait]
215
impl SimplyProtectedNoParametersHandler for TestDispatcher {
216
    type Action = TestActions;
217

            
218
1
    async fn resource_name<'a>(&'a self) -> Result<ResourceName<'a>, TestError> {
219
1
        Ok(ResourceName::named(0))
220
1
    }
221

            
222
1
    fn action() -> Self::Action {
223
1
        TestActions::DoSomething
224
1
    }
225

            
226
    async fn handle_protected(&self, _permissions: &Permissions) -> Result<Option<u64>, TestError> {
227
        Ok(None)
228
    }
229
}
230

            
231
#[async_trait::async_trait]
232
impl CustomProtectedNoParametersHandler for TestDispatcher {
233
1
    async fn verify_permissions(&self, permissions: &Permissions) -> Result<(), TestError> {
234
1
        if permissions.allowed_to(&ResourceName::named(0), &TestActions::DoSomething) {
235
            Ok(())
236
        } else {
237
1
            Err(TestError::CustomError)
238
        }
239
2
    }
240

            
241
    async fn handle_protected(&self, _permissions: &Permissions) -> Result<Option<u64>, TestError> {
242
        Ok(None)
243
    }
244
}
245

            
246
#[async_trait::async_trait]
247
impl CustomProtectedEnumParameterHandler for TestDispatcher {
248
3
    async fn verify_permissions(
249
3
        &self,
250
3
        permissions: &Permissions,
251
3
        arg1: &u64,
252
3
    ) -> Result<(), TestError> {
253
3
        if permissions.allowed_to(&ResourceName::named(*arg1), &TestActions::DoSomething) {
254
2
            Ok(())
255
        } else {
256
1
            Err(TestError::CustomError)
257
        }
258
6
    }
259

            
260
2
    async fn handle_protected(
261
2
        &self,
262
2
        _permissions: &Permissions,
263
2
        arg1: u64,
264
2
    ) -> Result<Option<u64>, TestError> {
265
2
        Ok(Some(arg1))
266
2
    }
267
}
268

            
269
#[async_trait::async_trait]
270
impl CustomProtectedStructParameterHandler for TestDispatcher {
271
2
    async fn verify_permissions(
272
2
        &self,
273
2
        permissions: &Permissions,
274
2
        arg1: &u64,
275
2
    ) -> Result<(), TestError> {
276
2
        if permissions.allowed_to(&ResourceName::named(*arg1), &TestActions::DoSomething) {
277
1
            Ok(())
278
        } else {
279
1
            Err(TestError::CustomError)
280
        }
281
4
    }
282

            
283
1
    async fn handle_protected(
284
1
        &self,
285
1
        _permissions: &Permissions,
286
1
        value: u64,
287
1
    ) -> Result<Option<u64>, TestError> {
288
1
        Ok(Some(value))
289
1
    }
290
}
291

            
292
4
#[derive(Actionable, Debug)]
293
#[actionable(actionable = crate, async)]
294
enum GenericRequest<T> {
295
    #[actionable(protection = "none")]
296
    NonGeneric,
297
    #[actionable(protection = "none", subaction)]
298
    Sub(T),
299
}
300

            
301
4
#[derive(AsyncDispatcher, Debug)]
302
#[dispatcher(
303
    input = GenericRequest<Request>,
304
    actionable = crate
305
)]
306
struct GenericDispatcher;
307

            
308
#[async_trait::async_trait]
309
impl GenericRequestDispatcher for GenericDispatcher {
310
    type Error = TestError;
311
    type Output = Option<u64>;
312
    type Subaction = Request;
313

            
314
1
    async fn handle_subaction(
315
1
        &self,
316
1
        permissions: &Permissions,
317
1
        subaction: Request,
318
1
    ) -> Result<Option<u64>, TestError> {
319
1
        TestDispatcher.dispatch(permissions, subaction).await
320
2
    }
321
}
322

            
323
#[async_trait::async_trait]
324
impl NonGenericHandler for GenericDispatcher {
325
1
    async fn handle(&self, permissions: &Permissions) -> Result<Option<u64>, TestError> {
326
1
        Ok(Some(52))
327
1
    }
328
}
329

            
330
1
#[tokio::test]
331
#[allow(clippy::too_many_lines)]
332
1
async fn example() {
333
1
    let permissions = Permissions::from(vec![Statement::for_resource(42).allowing_all()]);
334
1
    let dispatcher = TestDispatcher;
335

            
336
    // All success (permitted) cases
337
1
    assert_eq!(
338
1
        dispatcher
339
1
            .dispatch(&permissions, Request::UnprotectedEnumParameter(42),)
340
            .await
341
1
            .unwrap(),
342
        Some(42)
343
    );
344
1
    assert_eq!(
345
1
        dispatcher
346
1
            .dispatch(&permissions, Request::UnprotectedStructParameter {
347
1
                value: 42
348
1
            },)
349
            .await
350
1
            .unwrap(),
351
        Some(42)
352
    );
353
1
    assert_eq!(
354
1
        dispatcher
355
1
            .dispatch(&permissions, Request::UnprotectedNoParameters,)
356
            .await
357
1
            .unwrap(),
358
        None
359
    );
360
1
    assert_eq!(
361
1
        dispatcher
362
1
            .dispatch(&permissions, Request::SimplyProtectedEnumParameter(42))
363
            .await
364
1
            .unwrap(),
365
        Some(42)
366
    );
367
1
    assert_eq!(
368
1
        dispatcher
369
1
            .dispatch(&permissions, Request::SimplyProtectedStructParameter {
370
1
                value: 42
371
1
            },)
372
            .await
373
1
            .unwrap(),
374
        Some(42)
375
    );
376
1
    assert_eq!(
377
1
        dispatcher
378
1
            .dispatch(&permissions, Request::CustomProtectedEnumParameter(42))
379
            .await
380
1
            .unwrap(),
381
        Some(42)
382
    );
383
1
    assert_eq!(
384
1
        dispatcher
385
1
            .dispatch(&permissions, Request::CustomProtectedStructParameter {
386
1
                value: 42
387
1
            },)
388
            .await
389
1
            .unwrap(),
390
        Some(42)
391
    );
392

            
393
    // Generic dispatching
394
1
    assert!(matches!(
395
1
        GenericDispatcher
396
1
            .dispatch(&permissions, GenericRequest::NonGeneric,)
397
            .await,
398
        Ok(Some(52))
399
    ));
400
1
    assert!(matches!(
401
1
        GenericDispatcher
402
1
            .dispatch(
403
1
                &permissions,
404
1
                GenericRequest::Sub(Request::CustomProtectedEnumParameter(42)),
405
1
            )
406
            .await,
407
        Ok(Some(42))
408
    ));
409

            
410
    // Permission denied errors
411
1
    assert!(matches!(
412
1
        dispatcher
413
1
            .dispatch(&permissions, Request::SimplyProtectedNoParameters)
414
            .await,
415
        Err(TestError::PermissionDenied(_))
416
    ));
417
1
    assert!(matches!(
418
1
        dispatcher
419
1
            .dispatch(&permissions, Request::SimplyProtectedEnumParameter(1))
420
            .await,
421
        Err(TestError::PermissionDenied(_))
422
    ));
423
1
    assert!(matches!(
424
1
        dispatcher
425
1
            .dispatch(&permissions, Request::SimplyProtectedStructParameter {
426
1
                value: 1
427
1
            },)
428
            .await,
429
        Err(TestError::PermissionDenied(_))
430
    ));
431

            
432
    // Custom errors
433
1
    assert!(matches!(
434
1
        dispatcher
435
1
            .dispatch(&permissions, Request::CustomProtectedNoParameters)
436
            .await,
437
        Err(TestError::CustomError)
438
    ));
439
1
    assert!(matches!(
440
1
        dispatcher
441
1
            .dispatch(&permissions, Request::CustomProtectedEnumParameter(1))
442
            .await,
443
        Err(TestError::CustomError)
444
    ));
445
1
    assert!(matches!(
446
1
        dispatcher
447
1
            .dispatch(&permissions, Request::CustomProtectedStructParameter {
448
1
                value: 1
449
1
            },)
450
            .await,
451
        Err(TestError::CustomError)
452
    ));
453
1
}
454

            
455
1
#[test]
456
1
fn allowed_actions_merging_tests() {
457
1
    let permissions_a = Permissions::from(vec![
458
1
        Statement::for_resource(ResourceName::named("started_with_all")).allowing_all(),
459
1
        Statement::for_resource(ResourceName::named("started_with_some"))
460
1
            .allowing(&TestActions::DoSomething),
461
1
        Statement::for_resource(ResourceName::named("nested").and("started_with_all"))
462
1
            .allowing_all(),
463
1
        Statement::for_resource(ResourceName::named("nested").and("started_with_some"))
464
1
            .allowing(&TestActions::DoSomething),
465
1
    ]);
466
1
    let permissions_b = Permissions::from(vec![
467
1
        Statement::for_resource(ResourceName::named("started_with_none")).allowing_all(),
468
1
        Statement::for_resource(ResourceName::named("started_with_some")).allowing_all(),
469
1
        Statement::for_resource(ResourceName::named("nested").and("started_with_none"))
470
1
            .allowing(&TestActions::Post(PostActions::Read)),
471
1
        Statement::for_resource(ResourceName::named("nested").and("started_with_some"))
472
1
            .allowing(&TestActions::Post(PostActions::Read)),
473
1
    ]);
474
1

            
475
1
    let merged = Permissions::merged([permissions_a, permissions_b].iter());
476
1
    // For the top level, on Actions we're only testing transitioning form either
477
1
    // None/Some to All
478
1
    assert!(merged.allowed_to(
479
1
        &ResourceName::named("started_with_all"),
480
1
        &TestActions::DoSomething
481
1
    ));
482
1
    assert!(merged.allowed_to(
483
1
        &ResourceName::named("started_with_none"),
484
1
        &TestActions::DoSomething
485
1
    ));
486
1
    assert!(merged.allowed_to(
487
1
        &ResourceName::named("started_with_some"),
488
1
        &TestActions::DoSomething
489
1
    ));
490
1
    assert!(merged.allowed_to(
491
1
        &ResourceName::named("started_with_some"),
492
1
        &TestActions::Post(PostActions::Delete)
493
1
    ));
494
    // For the nested level, the transitions will only take permissions to a Some()
495
    // instead of All.
496
1
    assert!(merged.allowed_to(
497
1
        &ResourceName::named("nested").and("started_with_none"),
498
1
        &TestActions::Post(PostActions::Read)
499
1
    ));
500
1
    assert!(!merged.allowed_to(
501
1
        &ResourceName::named("nested").and("started_with_none"),
502
1
        &TestActions::DoSomething
503
1
    ));
504

            
505
1
    assert!(merged.allowed_to(
506
1
        &ResourceName::named("nested").and("started_with_some"),
507
1
        &TestActions::Post(PostActions::Read)
508
1
    ));
509
1
    assert!(merged.allowed_to(
510
1
        &ResourceName::named("nested").and("started_with_some"),
511
1
        &TestActions::DoSomething
512
1
    ));
513
1
}
514

            
515
1
#[test]
516
1
fn configuration_tests() {
517
1
    let permissions_a = Permissions::from(vec![
518
1
        Statement::for_resource(ResourceName::named("a")).with("u64", 0_u64),
519
1
        Statement::for_resource(ResourceName::named("a")).with("i64", i64::MIN),
520
1
        Statement::for_resource(ResourceName::named("a").and("b")).with("i64", 2_i64),
521
1
        Statement::for_any().with("global", "value"),
522
1
    ]);
523
1

            
524
1
    assert_eq!(
525
1
        permissions_a
526
1
            .get(&ResourceName::named("a"), "u64")
527
1
            .unwrap()
528
1
            .to_unsigned(),
529
1
        Some(0)
530
1
    );
531

            
532
1
    assert_eq!(
533
1
        permissions_a
534
1
            .get(&ResourceName::named("a"), "u64")
535
1
            .unwrap()
536
1
            .to_signed(),
537
1
        Some(0)
538
1
    );
539

            
540
1
    assert_eq!(
541
1
        permissions_a
542
1
            .get(&ResourceName::named("a"), "u64")
543
1
            .unwrap()
544
1
            .to_string(),
545
1
        "0"
546
1
    );
547

            
548
1
    assert_eq!(
549
1
        permissions_a
550
1
            .get(&ResourceName::named("a"), "i64")
551
1
            .unwrap()
552
1
            .to_unsigned(),
553
1
        None
554
1
    );
555

            
556
1
    assert_eq!(
557
1
        permissions_a
558
1
            .get(&ResourceName::named("a"), "i64")
559
1
            .unwrap()
560
1
            .to_signed(),
561
1
        Some(i64::MIN)
562
1
    );
563

            
564
1
    assert_eq!(
565
1
        permissions_a
566
1
            .get(&ResourceName::named("a").and("b"), "i64")
567
1
            .unwrap()
568
1
            .to_signed(),
569
1
        Some(2)
570
1
    );
571

            
572
1
    assert_eq!(
573
1
        permissions_a
574
1
            .get(&ResourceName::named("a").and("b"), "global")
575
1
            .unwrap()
576
1
            .to_string(),
577
1
        "value"
578
1
    );
579

            
580
1
    let permissions_b = Permissions::from(vec![
581
1
        Statement::for_resource(ResourceName::named("a")).with("newkey", "newvalue"),
582
1
        Statement::for_any().with("global", "value2"),
583
1
    ]);
584
1
    let merged = Permissions::merged([&permissions_a, &permissions_b]);
585
1

            
586
1
    assert_eq!(
587
1
        merged
588
1
            .get(&ResourceName::named("a").and("b"), "global")
589
1
            .unwrap()
590
1
            .to_string(),
591
1
        "value"
592
1
    );
593

            
594
1
    assert_eq!(
595
1
        merged
596
1
            .get(&ResourceName::named("a").and("b"), "newkey")
597
1
            .unwrap()
598
1
            .to_string(),
599
1
        "newvalue"
600
1
    );
601
1
}