1use std::collections::HashMap;
16
17use serde::{Deserialize, Serialize};
18use serde_json::value::Value;
19
20use crate::error::Error;
21use crate::token::{Token, TokenManager};
22
23pub struct PDPClient {
24 endpoint: String,
25 token_manager: TokenManager,
26}
27
28pub type Resource = HashMap<String, String>;
29
30#[derive(Debug, Clone, Deserialize, Serialize)]
31struct AuthorizeRequestBody(Vec<AuthorizeRequest>);
32
33#[derive(Debug, Clone, Deserialize, Serialize)]
34struct AuthorizeRequest {
35 subject: Subject,
36 action: String,
37 resource: ResourceAttrs,
38}
39
40#[derive(Debug, Clone, Deserialize, Serialize)]
41#[serde(rename_all = "camelCase")]
42pub struct Subject {
43 access_token_body: String,
44}
45
46#[derive(Debug, Clone, Deserialize, Serialize)]
47struct ResourceAttrs {
48 attributes: HashMap<String, String>,
49}
50
51impl From<Resource> for ResourceAttrs {
52 fn from(r: Resource) -> ResourceAttrs {
53 ResourceAttrs { attributes: r }
54 }
55}
56
57#[derive(Debug, Clone, Deserialize, Serialize)]
58struct AuthorizeResponseBody {
59 responses: Vec<AuthorizeResponse>,
60}
61
62#[derive(Debug, Clone, Deserialize, Serialize)]
63struct AuthorizeResponse {
64 #[serde(rename = "authorizationDecision")]
65 pub decision: AuthorizationDecision,
66
67 status: String,
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize)]
71pub struct AuthorizationDecision {
72 permitted: bool,
73 reason: Option<String>,
74 obligation: Option<Obligation>,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
78#[serde(rename_all = "camelCase")]
79pub struct Obligation {
80 actions: Vec<String>,
81 max_cache_age_seconds: u64,
82 subject: SubjectAttrs,
83}
84
85#[derive(Debug, Clone, Deserialize, Serialize)]
86struct SubjectAttrs {
87 attributes: HashMap<String, Value>,
88}
89
90impl PDPClient {
91 pub fn new(api_key: &str, endpoint: &str) -> Self {
92 Self {
93 endpoint: endpoint.to_string(),
94 token_manager: TokenManager::new(api_key, endpoint),
95 }
96 }
97
98 pub fn authorize(
99 &self,
100 subject: Subject,
101 action: &str,
102 resource: Resource,
103 ) -> Result<AuthorizationDecision, Error> {
104 let authreq = AuthorizeRequest {
105 subject: subject,
106 action: action.to_string(),
107 resource: resource.into(),
108 };
109
110 let req_body = serde_json::to_string(&AuthorizeRequestBody(vec![authreq])).unwrap();
111
112 let c = reqwest::blocking::Client::new();
113
114 let path = format!("{}/v2/authz", self.endpoint);
115
116 let token = self.token_manager.token()?.access_token;
117
118 let resp = c
119 .post(path)
120 .header("Accept", "application/json")
121 .header("Content-Type", "application/json")
122 .header("Authorization", format!("Bearer {}", token))
123 .body(req_body)
124 .send()
125 .expect("PDP Authorize request failed");
126
127 let status = resp.status();
128 let text = resp.text().expect("Getting body text failed");
129
130 if !status.is_success() {
131 return Err(
132 format!("Authz request failed: status='{}', body='{}'", status, text).into(),
133 );
134 }
135
136 let mut resp_body = match serde_json::from_str::<AuthorizeResponseBody>(&text) {
137 Ok(v) => v,
138 Err(_) => {
139 return Err(format!(
140 "Unexpected response from PDP: status='{}', body='{}'",
141 status, text
142 )
143 .into());
144 }
145 };
146
147 Ok(resp_body.responses.remove(0).decision)
148 }
149}
150
151pub fn subject_from_token(token: &Token) -> Subject {
152 let parts: Vec<&str> = token.access_token.split(".").collect();
153 Subject {
154 access_token_body: parts[1].to_string(),
155 }
156}