1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
use biscuit::CompactJson;
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde_json::Value;
use url::form_urlencoded::Serializer;
use crate::{
Bearer, Claims, Client, OAuth2Error, Provider,
error::ClientError,
uma2::{error::Uma2Error::*, permission_ticket::Uma2PermissionTicketRequest, *},
};
pub enum Uma2AuthenticationMethod {
Bearer,
Basic,
}
impl<P, C> Client<P, C>
where
P: Provider + Uma2Provider,
C: CompactJson + Claims,
{
///
/// Obtain an RPT from a UMA2 compliant OIDC server
///
/// # Arguments
/// * `token` Bearer token to do the RPT call
/// * `ticket` The most recent permission ticket received by the client as
/// part of the UMA authorization process
/// * `claim_token` A string representing additional claims that should be
/// considered by the server when evaluating permissions for the
/// resource(s) and scope(s) being requested.
/// * `claim_token_format` urn:ietf:params:oauth:token-type:jwt or https://openid.net/specs/openid-connect-core-1_0.html#IDToken
/// * `rpt` A previously issued RPT which permissions should also be
/// evaluated and added in a new one. This parameter allows clients in
/// possession of an RPT to perform incremental authorization where
/// permissions are added on demand.
/// * `permission` String representing a set of one or more resources and
/// scopes the client is seeking access. This parameter can be defined
/// multiple times in order to request permission for multiple resource
/// and scopes. This parameter is an extension to
/// urn:ietf:params:oauth:grant-type:uma-ticket grant type in order to
/// allow clients to send authorization requests without a permission
/// ticket
/// * `audience` The client identifier of the resource server to which the
/// client is seeking
/// access. This parameter is mandatory in case the permission parameter is
/// defined
/// * `response_include_resource_name` A boolean value indicating to the
/// server whether resource names should be included in the RPT’s
/// permissions. If false, only the resource identifier is included
/// * `response_permissions_limit` An integer N that defines a limit for the
/// amount of permissions an RPT can have. When used together with rpt
/// parameter, only the last N requested permissions will be kept in the
/// RPT.
/// * `submit_request` A boolean value indicating whether the server should
/// create permission requests to the resources and scopes referenced by a
/// permission ticket. This parameter only have effect if used together
/// with the ticket parameter as part of a UMA authorization process
#[allow(clippy::too_many_arguments)]
pub async fn obtain_requesting_party_token(
&self,
token: String,
auth_method: Uma2AuthenticationMethod,
ticket: impl Into<Option<String>>,
claim_token: impl Into<Option<String>>,
claim_token_format: impl Into<Option<Uma2ClaimTokenFormat>>,
rpt: impl Into<Option<String>>,
permission: impl Into<Option<Vec<String>>>,
audience: impl Into<Option<String>>,
response_include_resource_name: impl Into<Option<bool>>,
response_permissions_limit: impl Into<Option<u32>>,
submit_request: impl Into<Option<bool>>,
) -> Result<String, ClientError> {
if !self.provider.uma2_discovered() {
return Err(ClientError::Uma2(NoUma2Discovered));
}
let permission = permission.into();
let audience = audience.into();
if let Some(p) = permission.as_ref() {
if p.is_empty() && audience.is_none() {
return Err(ClientError::Uma2(AudienceFieldRequired));
}
}
let body = {
let mut body = Serializer::new(String::new());
body.append_pair("grant_type", "urn:ietf:params:oauth:grant-type:uma-ticket");
if let Some(ticket) = ticket.into() {
body.append_pair("ticket", &ticket);
}
if let Some(claim_token) = claim_token.into() {
body.append_pair("claim_token", &claim_token);
}
if let Some(claim_token_format) = claim_token_format.into() {
body.append_pair(
"claim_token_format",
claim_token_format.to_string().as_str(),
);
}
if let Some(rpt) = rpt.into() {
body.append_pair("rpt", &rpt);
}
if let Some(permission) = permission {
permission.iter().for_each(|perm| {
body.append_pair("permission", perm.as_str());
});
}
if let Some(audience) = audience {
body.append_pair("audience", &audience);
}
if let Some(response_include_resource_name) = response_include_resource_name.into() {
body.append_pair(
"response_include_resource_name",
if response_include_resource_name {
"true"
} else {
"false"
},
);
}
if let Some(response_permissions_limit) = response_permissions_limit.into() {
body.append_pair(
"response_permissions_limit",
format!("{:}", response_permissions_limit).as_str(),
);
}
if let Some(submit_request) = submit_request.into() {
body.append_pair("submit_request", format!("{:}", submit_request).as_str());
}
body.finish()
};
let auth_method = match auth_method {
Uma2AuthenticationMethod::Basic => format!("Basic {:}", token),
Uma2AuthenticationMethod::Bearer => format!("Bearer {:}", token),
};
let json = self
.http_client
.post(self.provider.token_uri().clone())
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.header(AUTHORIZATION, auth_method.as_str())
.body(body)
.send()
.await?
.json::<Value>()
.await?;
let error: Result<OAuth2Error, _> = serde_json::from_value(json.clone());
if let Ok(error) = error {
Err(ClientError::from(error))
} else {
let new_token: Bearer = serde_json::from_value(json)?;
Ok(new_token.access_token)
}
}
///
/// Create a permission ticket.
/// A permission ticket is a special security token type representing a
/// permission request. Per the UMA specification, a permission ticket
/// is: A correlation handle that is conveyed from an authorization
/// server to a resource server, from a resource server to a client, and
/// ultimately from a client back to an authorization server, to enable
/// the authorization server to assess the correct policies to apply to a
/// request for authorization data.
///
/// # Arguments
/// * `pat_token` A Protection API token (PAT) is like any OAuth2 token, but
/// should have the
/// * `requests` A list of resources, optionally with their scopes,
/// optionally with extra claims to be processed.
pub async fn create_uma2_permission_ticket(
&self,
pat_token: String,
requests: Vec<Uma2PermissionTicketRequest>,
) -> Result<Uma2PermissionTicketResponse, ClientError> {
if !self.provider.uma2_discovered() {
return Err(ClientError::Uma2(NoUma2Discovered));
}
let Some(url) = self.provider.permission_uri().cloned() else {
return Err(ClientError::Uma2(NoPermissionsEndpoint));
};
let json = self
.http_client
.post(url)
.header(CONTENT_TYPE, "application/json")
.header(AUTHORIZATION, format!("Bearer {:}", pat_token))
.json(&requests)
.send()
.await?
.json::<Value>()
.await?;
let error: Result<OAuth2Error, _> = serde_json::from_value(json.clone());
if let Ok(error) = error {
Err(ClientError::from(error))
} else {
let response = serde_json::from_value(json)?;
Ok(response)
}
}
}