Skip to main content

lys_pass/
refusal.rs

1//! One permission refusal shape for every product route.
2
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::{Error, Target};
7
8/// The grant the caller needs to request.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(deny_unknown_fields)]
11pub struct Needed {
12    /// The application audience.
13    pub app: String,
14    /// The exact resource kind.
15    pub kind: String,
16    /// The exact resource id.
17    pub id: String,
18    /// The requested action.
19    pub action: String,
20}
21
22/// The contents of the shared refusal envelope.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct Refused {
26    /// The resource as kind and id.
27    pub resource: String,
28    /// The action refused.
29    pub action: String,
30    /// The grant that would permit it.
31    pub needed: Needed,
32    /// The Lys address at which to request that grant.
33    pub request: String,
34}
35
36/// A serialized refusal with no product-specific outer shape.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(deny_unknown_fields)]
39pub struct Refusal {
40    /// The refused act and how to request permission.
41    pub refused: Refused,
42}
43
44impl Refusal {
45    /// Name an exact needed grant and the issuer's existing request screen.
46    pub fn new(app: &str, kind: &str, id: &str, action: &str, issuer: &str) -> Result<Self, Error> {
47        let target = Target::new(kind, id, action)?;
48        if app.is_empty() || !crate::rights::audience_owns(kind, app) {
49            return Err(Error::RightsOutsideAudience);
50        }
51        let mut base = Url::parse(issuer)?;
52        if !matches!(base.scheme(), "http" | "https")
53            || base.host_str().is_none()
54            || !base.username().is_empty()
55            || base.password().is_some()
56            || base.query().is_some()
57            || base.fragment().is_some()
58        {
59            return Err(Error::Invalid("grant request issuer is invalid"));
60        }
61        if !base.path().ends_with('/') {
62            base.set_path(&format!("{}/", base.path()));
63        }
64        let mut request = base;
65        request.set_fragment(Some("/requests"));
66        Ok(Self {
67            refused: Refused {
68                resource: format!("{kind}:{id}"),
69                action: target.action.clone(),
70                needed: Needed {
71                    app: app.to_owned(),
72                    kind: target.kind,
73                    id: target.id,
74                    action: target.action,
75                },
76                request: request.into(),
77            },
78        })
79    }
80}