1use chrono::{DateTime, FixedOffset};
18use serde::ser::SerializeStruct;
19use serde::{Deserialize, Serialize, Serializer};
20
21use crate::common::IdOrName;
22
23#[derive(Clone, Debug, Serialize)]
25pub struct UserAndPassword {
26 #[serde(flatten)]
27 pub user: IdOrName,
28 pub password: String,
29 #[serde(skip_serializing_if = "Option::is_none", default)]
30 pub domain: Option<IdOrName>,
31}
32
33#[derive(Clone, Debug, Serialize)]
35pub struct ApplicationCredential {
36 #[serde(flatten)]
37 pub id: IdOrName,
38 pub secret: String,
39 #[serde(skip_serializing_if = "Option::is_none", default)]
40 pub user: Option<IdOrName>,
41}
42
43#[derive(Clone, Debug)]
45pub enum Identity {
46 Password(UserAndPassword),
48 Token(String),
50 ApplicationCredential(ApplicationCredential),
52}
53
54#[derive(Clone, Debug, Serialize)]
56pub struct Project {
57 #[serde(flatten)]
58 pub project: IdOrName,
59 #[serde(skip_serializing_if = "Option::is_none", default)]
60 pub domain: Option<IdOrName>,
61}
62
63#[allow(unused)]
65#[derive(Clone, Debug, Serialize)]
66pub enum Scope {
67 #[serde(rename = "project")]
69 Project(Project),
70 #[serde(rename = "domain")]
72 Domain(IdOrName),
73 #[serde(rename = "system", serialize_with = "ser_system_scope")]
74 System,
75}
76
77#[derive(Clone, Debug, Serialize)]
79pub struct Auth {
80 pub identity: Identity,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub scope: Option<Scope>,
85}
86
87#[derive(Clone, Debug, Serialize)]
89pub struct AuthRoot {
90 pub auth: Auth,
91}
92
93#[derive(Clone, Debug, Deserialize)]
95pub struct Endpoint {
96 pub interface: String,
97 pub region: String,
98 pub url: String,
99}
100
101#[derive(Clone, Debug, Deserialize)]
103pub struct CatalogRecord {
104 #[serde(rename = "type")]
105 pub service_type: String,
106 pub endpoints: Vec<Endpoint>,
107}
108
109#[derive(Clone, Debug, Deserialize)]
111pub struct Token {
112 pub expires_at: DateTime<FixedOffset>,
113 pub catalog: Vec<CatalogRecord>,
114}
115
116#[derive(Clone, Debug, Deserialize)]
118pub struct TokenRoot {
119 pub token: Token,
120}
121
122#[derive(Debug, Serialize)]
123struct PasswordAuth<'a> {
124 user: &'a UserAndPassword,
125}
126
127#[derive(Debug, Serialize)]
128struct TokenAuth<'a> {
129 id: &'a str,
130}
131
132impl IdOrName {
133 #[inline]
135 pub fn from_id<T: Into<String>>(id: T) -> IdOrName {
136 IdOrName::Id(id.into())
137 }
138
139 #[inline]
141 pub fn from_name<T: Into<String>>(name: T) -> IdOrName {
142 IdOrName::Name(name.into())
143 }
144}
145
146impl Serialize for Identity {
147 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
148 where
149 S: Serializer,
150 {
151 let mut inner = serializer.serialize_struct("Identity", 2)?;
152 match self {
153 Identity::Password(ref user) => {
154 inner.serialize_field("methods", &["password"])?;
155 inner.serialize_field("password", &PasswordAuth { user })?;
156 }
157 Identity::Token(ref token) => {
158 inner.serialize_field("methods", &["token"])?;
159 inner.serialize_field("token", &TokenAuth { id: token })?;
160 }
161 Identity::ApplicationCredential(ref cred) => {
162 inner.serialize_field("methods", &["application_credential"])?;
163 inner.serialize_field("application_credential", &cred)?;
164 }
165 }
166 inner.end()
167 }
168}
169
170fn ser_system_scope<S>(serializer: S) -> Result<S::Ok, S::Error>
171where
172 S: Serializer,
173{
174 let mut inner = serializer.serialize_struct("System", 1)?;
175 inner.serialize_field("all", &true)?;
176 inner.end()
177}
178
179#[cfg(test)]
180mod test {
181 use super::*;
182 use crate::common::test;
183
184 const PASSWORD_NAME_UNSCOPED: &str = r#"
185{
186 "auth": {
187 "identity": {
188 "methods": [
189 "password"
190 ],
191 "password": {
192 "user": {
193 "name": "admin",
194 "domain": {
195 "name": "Default"
196 },
197 "password": "devstacker"
198 }
199 }
200 }
201 }
202}"#;
203
204 const PASSWORD_ID_SCOPED_WITH_ID: &str = r#"
205{
206 "auth": {
207 "identity": {
208 "methods": [
209 "password"
210 ],
211 "password": {
212 "user": {
213 "id": "ee4dfb6e5540447cb3741905149d9b6e",
214 "password": "devstacker"
215 }
216 }
217 },
218 "scope": {
219 "domain": {
220 "id": "default"
221 }
222 }
223 }
224}"#;
225
226 const PASSWORD_ID_SYSTEM_SCOPE: &str = r#"
227{
228 "auth": {
229 "identity": {
230 "methods": [
231 "password"
232 ],
233 "password": {
234 "user": {
235 "id": "ee4dfb6e5540447cb3741905149d9b6e",
236 "password": "devstacker"
237 }
238 }
239 },
240 "scope": {
241 "system": {
242 "all": true
243 }
244 }
245 }
246}"#;
247
248 const TOKEN_SCOPED_WITH_NAME: &str = r#"
249{
250 "auth": {
251 "identity": {
252 "methods": [
253 "token"
254 ],
255 "token": {
256 "id": "abcdef"
257 }
258 },
259 "scope": {
260 "domain": {
261 "name": "Default"
262 }
263 }
264 }
265}"#;
266
267 const APPLICATION_CREDENTIAL_ID: &str = r#"
268{
269 "auth": {
270 "identity": {
271 "methods": [
272 "application_credential"
273 ],
274 "application_credential": {
275 "id": "abcdef",
276 "secret": "shhhh"
277 }
278 }
279 }
280}"#;
281
282 const APPLICATION_CREDENTIAL_NAME: &str = r#"
283{
284 "auth": {
285 "identity": {
286 "methods": [
287 "application_credential"
288 ],
289 "application_credential": {
290 "name": "abcdef",
291 "secret": "shhhh",
292 "user": {
293 "id": "a6b3c6e7a6d"
294 }
295 }
296 }
297 }
298}"#;
299
300 #[test]
301 fn test_password_name_unscoped() {
302 let value = AuthRoot {
303 auth: Auth {
304 identity: Identity::Password(UserAndPassword {
305 user: IdOrName::Name("admin".to_string()),
306 password: "devstacker".to_string(),
307 domain: Some(IdOrName::from_name("Default")),
308 }),
309 scope: None,
310 },
311 };
312 test::compare(PASSWORD_NAME_UNSCOPED, value);
313 }
314
315 #[test]
316 fn test_password_id_scoped_with_id() {
317 let value = AuthRoot {
318 auth: Auth {
319 identity: Identity::Password(UserAndPassword {
320 user: IdOrName::Id("ee4dfb6e5540447cb3741905149d9b6e".to_string()),
321 password: "devstacker".to_string(),
322 domain: None,
323 }),
324 scope: Some(Scope::Domain(IdOrName::from_id("default"))),
325 },
326 };
327 test::compare(PASSWORD_ID_SCOPED_WITH_ID, value);
328 }
329
330 #[test]
331 fn test_password_id_system_scope() {
332 let value = AuthRoot {
333 auth: Auth {
334 identity: Identity::Password(UserAndPassword {
335 user: IdOrName::Id("ee4dfb6e5540447cb3741905149d9b6e".to_string()),
336 password: "devstacker".to_string(),
337 domain: None,
338 }),
339 scope: Some(Scope::System),
340 },
341 };
342 test::compare(PASSWORD_ID_SYSTEM_SCOPE, value);
343 }
344
345 #[test]
346 fn test_token_scoped_with_name() {
347 let value = AuthRoot {
348 auth: Auth {
349 identity: Identity::Token("abcdef".to_string()),
350 scope: Some(Scope::Domain(IdOrName::Name("Default".to_string()))),
351 },
352 };
353 test::compare(TOKEN_SCOPED_WITH_NAME, value);
354 }
355
356 #[test]
357 fn test_application_credential_id() {
358 let value = AuthRoot {
359 auth: Auth {
360 identity: Identity::ApplicationCredential(ApplicationCredential {
361 id: IdOrName::Id("abcdef".to_string()),
362 secret: "shhhh".to_string(),
363 user: None,
364 }),
365 scope: None,
366 },
367 };
368 test::compare(APPLICATION_CREDENTIAL_ID, value);
369 }
370
371 #[test]
372 fn test_application_credential_name() {
373 let value = AuthRoot {
374 auth: Auth {
375 identity: Identity::ApplicationCredential(ApplicationCredential {
376 id: IdOrName::Name("abcdef".to_string()),
377 secret: "shhhh".to_string(),
378 user: Some(IdOrName::Id("a6b3c6e7a6d".into())),
379 }),
380 scope: None,
381 },
382 };
383 test::compare(APPLICATION_CREDENTIAL_NAME, value);
384 }
385}