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    ///
47    /// # Errors
48    ///
49    /// Refuses an empty or control-bearing target, a missing application
50    /// or a kind that application does not own. The issuer must parse as
51    /// an HTTP or HTTPS address with a host and no credentials, query or
52    /// fragment; an invalid address is refused before a link is returned.
53    pub fn new(app: &str, kind: &str, id: &str, action: &str, issuer: &str) -> Result<Self, Error> {
54        let target = Target::new(kind, id, action)?;
55        if app.is_empty() || !crate::rights::audience_owns(kind, app) {
56            return Err(Error::RightsOutsideAudience);
57        }
58        let mut base = Url::parse(issuer)?;
59        if !matches!(base.scheme(), "http" | "https")
60            || base.host_str().is_none()
61            || !base.username().is_empty()
62            || base.password().is_some()
63            || base.query().is_some()
64            || base.fragment().is_some()
65        {
66            return Err(Error::Invalid("grant request issuer is invalid"));
67        }
68        if !base.path().ends_with('/') {
69            base.set_path(&format!("{}/", base.path()));
70        }
71        let mut request = base;
72        request.set_fragment(Some("/requests"));
73        Ok(Self {
74            refused: Refused {
75                resource: format!("{kind}:{id}"),
76                action: target.action.clone(),
77                needed: Needed {
78                    app: app.to_owned(),
79                    kind: target.kind,
80                    id: target.id,
81                    action: target.action,
82                },
83                request: request.into(),
84            },
85        })
86    }
87}