Skip to main content

solid_pod_rs/wac/
client.rs

1//! `acl:ClientCondition` — gate authorisation on the requesting client.
2//!
3//! A `ClientCondition` is satisfied when the request context's
4//! `client_id` matches one of the listed `acl:client` IRIs, when the
5//! client is a member of a listed `acl:clientGroup`, or when the
6//! condition names a public class (`foaf:Agent`).
7
8use serde::{Deserialize, Serialize};
9
10use crate::wac::conditions::{ConditionOutcome, RequestContext};
11use crate::wac::document::{get_ids, IdOrIds};
12use crate::wac::evaluator::GroupMembership;
13
14/// Body of an `acl:ClientCondition`.
15///
16/// Fields mirror the WAC 2.0 predicates: `acl:client`, `acl:clientGroup`,
17/// `acl:clientClass`. Any subset may be populated; evaluation is OR
18/// across the populated predicates (i.e. a client matching any of them
19/// satisfies the condition).
20#[derive(Debug, Clone, Default, Deserialize, Serialize)]
21pub struct ClientConditionBody {
22    #[serde(
23        rename = "acl:client",
24        default,
25        skip_serializing_if = "Option::is_none"
26    )]
27    pub client: Option<IdOrIds>,
28
29    #[serde(
30        rename = "acl:clientGroup",
31        default,
32        skip_serializing_if = "Option::is_none"
33    )]
34    pub client_group: Option<IdOrIds>,
35
36    #[serde(
37        rename = "acl:clientClass",
38        default,
39        skip_serializing_if = "Option::is_none"
40    )]
41    pub client_class: Option<IdOrIds>,
42}
43
44/// Default evaluator for `acl:ClientCondition`.
45///
46/// The evaluator is stateless; it closes over the request context and
47/// (optionally) a group resolver supplied by `evaluate_access_ctx`.
48/// For the first-cut implementation, groups are resolved via the same
49/// `GroupMembership` trait used by `acl:agentGroup`.
50#[derive(Debug, Default, Clone, Copy)]
51pub struct ClientConditionEvaluator;
52
53impl ClientConditionEvaluator {
54    pub fn evaluate(
55        &self,
56        body: &ClientConditionBody,
57        ctx: &RequestContext<'_>,
58        groups: &dyn GroupMembership,
59    ) -> ConditionOutcome {
60        // Public class shortcut.
61        for cls in get_ids(&body.client_class) {
62            if cls == "foaf:Agent" || cls == "http://xmlns.com/foaf/0.1/Agent" {
63                return ConditionOutcome::Satisfied;
64            }
65        }
66
67        // Direct client-id match.
68        if let Some(cid) = ctx.client_id {
69            for c in get_ids(&body.client) {
70                if c == cid {
71                    return ConditionOutcome::Satisfied;
72                }
73            }
74            // Group membership — reuses the same resolver as agentGroup
75            // because WAC 2.0 treats group documents uniformly.
76            for g in get_ids(&body.client_group) {
77                if groups.is_member(g, cid) {
78                    return ConditionOutcome::Satisfied;
79                }
80            }
81        }
82
83        // No predicate matched. This is `Denied` (definite no-match),
84        // not `NotApplicable` — which is reserved for unknown condition
85        // types that the registry cannot dispatch at all.
86        ConditionOutcome::Denied
87    }
88}