Skip to main content

ishare/
pdp.rs

1use crate::delegation_evidence::{
2    build_filter_delegation_request, verify_delegation_evidence, DelegationEvidence,
3    DelegationTarget, Environment, Policy, Resource, ResourceRules, ResourceTarget,
4};
5use crate::ishare;
6
7use super::delegation_request::{build_simple_delegation_request, DelegationRequestContainer};
8use super::ishare::{IshareClaimsWithExtra, IshareError, ISHARE};
9use base64::prelude::*;
10
11use chrono::Utc;
12use jsonwebtoken::TokenData;
13use reqwest::header::AUTHORIZATION;
14use reqwest::StatusCode;
15use serde::{Deserialize, Serialize};
16use uuid::Uuid;
17
18const YEAR_SECONDS: i64 = 31556926;
19
20#[derive(Deserialize)]
21pub struct LoginResponse {
22    pub access_token: String,
23    pub expires_in: i64,
24}
25
26#[derive(Deserialize)]
27pub struct DelegationTokenResponse {
28    delegation_token: String,
29}
30
31pub struct PDP<'a> {
32    ishare: &'a ISHARE,
33    eori: String,
34    url: String,
35}
36
37#[derive(Deserialize)]
38struct DelegationTokenClaims {
39    #[serde(rename = "delegationEvidence")]
40    pub delegation_evidence: DelegationEvidence,
41}
42
43#[derive(Serialize)]
44#[serde(rename_all = "camelCase")]
45struct PolicySetInsert {
46    policy_issuer: String,
47    licences: Vec<String>,
48    max_delegation_depth: i32,
49    target: DelegationTarget,
50    policies: Vec<Policy>,
51}
52
53#[derive(Deserialize)]
54pub struct PolicySetInsertResponse {
55    pub uuid: Uuid,
56}
57
58impl<'a> PDP<'a> {
59    pub fn new(ishare: &'a ISHARE, eori: String, url: String) -> PDP<'a> {
60        return Self { ishare, eori, url };
61    }
62
63    pub async fn authorize(
64        &self,
65        access_token: &str,
66        action: &str,
67        access_subject: &str,
68        policy_issuer: &str,
69        resource_type: &str,
70        identifiers: Option<Vec<String>>,
71        attributes: Option<Vec<String>>,
72    ) -> Result<bool, IshareError> {
73        let delegation_request = self.create_delegation_request(
74            action,
75            access_subject,
76            policy_issuer,
77            resource_type,
78            identifiers,
79            attributes,
80        );
81
82        let delegation_token = self.call_pdp(access_token, &delegation_request).await?;
83
84        return self
85            .check_delegation_token(&delegation_token, resource_type)
86            .map_err(|e| IshareError {
87                message: format!("{:?}", e),
88            });
89    }
90
91    fn check_delegation_token(
92        &self,
93        delegation_token: &str,
94        resource_type: &str,
95    ) -> Result<bool, ishare::DecodeTokenError> {
96        let decoded: TokenData<IshareClaimsWithExtra<DelegationTokenClaims>> =
97            self.ishare
98                .decode_token_custom_claims::<DelegationTokenClaims>(delegation_token, None)?;
99
100        let de = decoded.claims.extra.delegation_evidence;
101
102        let authorized = verify_delegation_evidence(&de, resource_type.to_string());
103
104        return Ok(authorized);
105    }
106
107    async fn call_pdp(
108        &self,
109        access_token: &str,
110        delegation_request: &DelegationRequestContainer,
111    ) -> Result<String, IshareError> {
112        let target_url = format!("{}/delegation", self.url);
113
114        let response = reqwest::Client::new()
115            .post(target_url)
116            .header("Authorization", format!("Bearer {access_token}"))
117            .json(delegation_request)
118            .send()
119            .await
120            .map_err(|e| IshareError {
121                message: e.to_string(),
122            })?
123            .error_for_status()
124            .map_err(|e| IshareError {
125                message: e.to_string(),
126            })?
127            .json::<DelegationTokenResponse>()
128            .await
129            .map_err(|e| IshareError {
130                message: e.to_string(),
131            })?;
132
133        Ok(response.delegation_token)
134    }
135
136    pub async fn put_policy_set(
137        &self,
138        access_token: &str,
139        policy_issuer: &str,
140        access_subject: &str,
141        service_provider: &str,
142        resource_type: String,
143        actions: Vec<String>,
144        identifiers: Option<Vec<String>>,
145    ) -> Result<PolicySetInsertResponse, IshareError> {
146        let target_url = format!("{}/policy-set", self.url);
147
148        let actual_identifiers = match identifiers {
149            None => vec!["*".to_owned()],
150            Some(id) => id,
151        };
152
153        let actual_attributes = vec!["*".to_owned()];
154
155        let service_providers = vec![service_provider.to_owned()];
156
157        let new_policy = Policy {
158            rules: vec![ResourceRules {
159                effect: "Permit".to_owned(),
160            }],
161            target: ResourceTarget {
162                actions,
163                resource: Resource {
164                    resource_type,
165                    identifiers: actual_identifiers,
166                    attributes: actual_attributes,
167                },
168                environment: Some(Environment { service_providers }),
169            },
170        };
171
172        let policy_set = PolicySetInsert {
173            policy_issuer: policy_issuer.to_owned(),
174            licences: vec!["ISHARE.0001".to_owned()],
175            max_delegation_depth: 1,
176            policies: vec![new_policy],
177            target: DelegationTarget {
178                access_subject: access_subject.to_owned(),
179            },
180        };
181
182        let response = reqwest::Client::new()
183            .post(target_url)
184            .header("Authorization", format!("Bearer {access_token}"))
185            .json(&policy_set)
186            .send()
187            .await
188            .map_err(|e| IshareError {
189                message: e.to_string(),
190            })?
191            .error_for_status()
192            .map_err(|e| IshareError {
193                message: e.to_string(),
194            })?
195            .json::<PolicySetInsertResponse>()
196            .await
197            .map_err(|e| IshareError {
198                message: e.to_string(),
199            })?;
200
201        Ok(response)
202    }
203
204    pub async fn remove_policy_set(
205        &self,
206        access_token: &str,
207        policy_set_id: &str,
208    ) -> Result<(), IshareError> {
209        let target_url = format!("{}/policy-set/{}", self.url, policy_set_id);
210
211        reqwest::Client::new()
212            .delete(target_url)
213            .header("Authorization", format!("Bearer {access_token}"))
214            .send()
215            .await
216            .map_err(|e| IshareError {
217                message: e.to_string(),
218            })?
219            .error_for_status()
220            .map_err(|e| IshareError {
221                message: e.to_string(),
222            })?;
223
224        Ok(())
225    }
226
227    pub async fn put_policy_filter(
228        &self,
229        access_token: &str,
230        policy_issuer: &str,
231        access_subject: &str,
232        service_provider: &str,
233        resource_type: String,
234        actions: Vec<String>,
235        identifiers: Vec<String>,
236    ) -> Result<DelegationEvidence, IshareError> {
237        let target_url = format!("{}/ar/policy", self.url);
238        let policies_delegation_response = self
239            .get_policies(&access_token, policy_issuer, access_subject)
240            .await?;
241
242        let policies = match policies_delegation_response {
243            Some(de) => de.policy_sets.get(0).map_or(vec![], |p| p.policies.clone()),
244            None => vec![],
245        };
246
247        let now = Utc::now().timestamp();
248        let delegation_request = build_filter_delegation_request(
249            now,
250            now + YEAR_SECONDS,
251            policy_issuer.to_owned(),
252            access_subject.to_owned(),
253            resource_type,
254            service_provider.to_owned(),
255            actions,
256            identifiers,
257            vec!["*".to_owned()],
258            policies,
259        );
260
261        let response = reqwest::Client::new()
262            .post(target_url)
263            .header("Authorization", format!("Bearer {access_token}"))
264            .json(&delegation_request)
265            .send()
266            .await
267            .map_err(|e| IshareError {
268                message: e.to_string(),
269            })?
270            .error_for_status()
271            .map_err(|e| IshareError {
272                message: e.to_string(),
273            })?
274            .json::<DelegationEvidence>()
275            .await
276            .map_err(|e| IshareError {
277                message: e.to_string(),
278            })?;
279
280        Ok(response)
281    }
282
283    async fn get_policies(
284        &self,
285        access_token: &str,
286        issuer: &str,
287        access_subject: &str,
288    ) -> Result<Option<DelegationEvidence>, IshareError> {
289        let target_url = format!(
290            "{}/ar/policy?issuer={issuer}&access_subject={access_subject}",
291            self.url
292        );
293        let response = reqwest::Client::new()
294            .get(target_url)
295            .header("Authorization", format!("Bearer {access_token}"))
296            .send()
297            .await
298            .map_err(|e| IshareError {
299                message: e.to_string(),
300            })?;
301
302        if response.status() == StatusCode::NOT_FOUND {
303            return Ok(None);
304        }
305
306        let result = response
307            .error_for_status()
308            .map_err(|e| IshareError {
309                message: e.to_string(),
310            })?
311            .json::<DelegationEvidence>()
312            .await
313            .map_err(|e| IshareError {
314                message: e.to_string(),
315            })?;
316
317        return Ok(Some(result));
318    }
319
320    fn create_delegation_request(
321        &self,
322        action: &str,
323        access_subject: &str,
324        policy_issuer: &str,
325        resource_type: &str,
326        identifiers: Option<Vec<String>>,
327        attributes: Option<Vec<String>>,
328    ) -> DelegationRequestContainer {
329        let service_provider = self.ishare.get_client_eori();
330        let actions = vec![action.to_string()];
331
332        let delegation_request = build_simple_delegation_request(
333            policy_issuer.to_string(),
334            access_subject.to_string(),
335            resource_type.to_string(),
336            service_provider,
337            actions,
338            identifiers,
339            attributes,
340        );
341
342        delegation_request
343    }
344
345    pub async fn connect_admin(
346        &self,
347        admin_username: &str,
348        admin_password: &str,
349        pdp_app_id: &str,
350        pdp_app_secret: &str,
351    ) -> Result<LoginResponse, IshareError> {
352        let form_data = vec![
353            ("grant_type", "password"),
354            ("username", admin_username),
355            ("password", admin_password),
356        ];
357
358        let target_url = format!("{}/oauth2/token", self.url);
359        let basic_auth = base64::engine::general_purpose::STANDARD
360            .encode(format!("{pdp_app_id}:{pdp_app_secret}"));
361
362        let response = reqwest::Client::new()
363            .post(target_url)
364            .form(&form_data)
365            .header("Content-Type", "application/x-www-form-urlencoded")
366            .header(AUTHORIZATION, format!("Basic {basic_auth}"))
367            .send()
368            .await
369            .map_err(|e| IshareError {
370                message: e.to_string(),
371            })?
372            .error_for_status()
373            .map_err(|e| IshareError {
374                message: e.to_string(),
375            })?
376            .json::<LoginResponse>()
377            .await
378            .map_err(|e| IshareError {
379                message: e.to_string(),
380            })?;
381
382        return Ok(response);
383    }
384
385    pub async fn connect(&self) -> Result<LoginResponse, IshareError> {
386        let client_assertion = self.ishare.create_client_assertion(self.eori.clone())?;
387
388        let client_id = self.ishare.get_client_eori();
389
390        let form_data = vec![
391            ("grant_type", "client_credentials"),
392            (
393                "client_assertion_type",
394                "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
395            ),
396            ("scope", "iSHARE"),
397            ("client_id", &client_id),
398            ("client_assertion", &client_assertion),
399        ];
400
401        let target_url = format!("{}/connect/machine/token", self.url);
402
403        let response = reqwest::Client::new()
404            .post(target_url)
405            .form(&form_data)
406            .header("Content-Type", "application/x-www-form-urlencoded")
407            .send()
408            .await
409            .map_err(|e| IshareError {
410                message: e.to_string(),
411            })?
412            .error_for_status()
413            .map_err(|e| IshareError {
414                message: e.to_string(),
415            })?
416            .json::<LoginResponse>()
417            .await
418            .map_err(|e| IshareError {
419                message: e.to_string(),
420            })?;
421
422        return Ok(response);
423    }
424}