Skip to main content

openstack_sdk_identity/v3/auth/os_federation/saml2/
create.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13// SPDX-License-Identifier: Apache-2.0
14//
15// WARNING: This file is automatically generated from OpenAPI schema using
16// `openstack-codegenerator`.
17
18//! Exchange a scoped token for a SAML assertion.
19//!
20//! POST /v3/auth/OS-FEDERATION/saml2
21//!
22use derive_builder::Builder;
23use http::{HeaderMap, HeaderName, HeaderValue};
24
25use openstack_sdk_core::api::rest_endpoint_prelude::*;
26
27use openstack_sdk_core::api::common::serialize_sensitive_optional_string;
28use openstack_sdk_core::api::common::serialize_sensitive_string;
29use secrecy::SecretString;
30use serde::Deserialize;
31use serde::Serialize;
32use std::borrow::Cow;
33
34/// A `domain` object
35#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
36#[builder(setter(strip_option))]
37pub struct Domain<'a> {
38    /// User Domain ID
39    #[serde(skip_serializing_if = "Option::is_none")]
40    #[builder(default, setter(into))]
41    pub(crate) id: Option<Cow<'a, str>>,
42
43    /// User Domain Name
44    #[serde(skip_serializing_if = "Option::is_none")]
45    #[builder(default, setter(into))]
46    pub(crate) name: Option<Cow<'a, str>>,
47}
48
49/// A user object, required if an application credential is identified by name
50/// and not ID.
51#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
52#[builder(setter(strip_option))]
53pub struct User<'a> {
54    /// A `domain` object
55    #[serde(skip_serializing_if = "Option::is_none")]
56    #[builder(default, setter(into))]
57    pub(crate) domain: Option<Domain<'a>>,
58
59    /// The user ID
60    #[serde(skip_serializing_if = "Option::is_none")]
61    #[builder(default, setter(into))]
62    pub(crate) id: Option<Cow<'a, str>>,
63
64    /// The user name
65    #[serde(skip_serializing_if = "Option::is_none")]
66    #[builder(default, setter(into))]
67    pub(crate) name: Option<Cow<'a, str>>,
68}
69
70/// An application credential object.
71#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
72#[builder(setter(strip_option))]
73pub struct ApplicationCredential<'a> {
74    /// The ID of the application credential used for authentication. If not
75    /// provided, the application credential must be identified by its name and
76    /// its owning user.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    #[builder(default, setter(into))]
79    pub(crate) id: Option<Cow<'a, str>>,
80
81    /// The name of the application credential used for authentication. If
82    /// provided, must be accompanied by a user object.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    #[builder(default, setter(into))]
85    pub(crate) name: Option<Cow<'a, str>>,
86
87    /// The secret for authenticating the application credential.
88    #[serde(serialize_with = "serialize_sensitive_string")]
89    #[builder(setter(into))]
90    pub(crate) secret: SecretString,
91
92    /// A user object, required if an application credential is identified by
93    /// name and not ID.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    #[builder(default, setter(into))]
96    pub(crate) user: Option<User<'a>>,
97}
98
99#[derive(Debug, Deserialize, Clone, Serialize)]
100pub enum Methods {
101    #[serde(rename = "application_credential")]
102    ApplicationCredential,
103    #[serde(rename = "password")]
104    Password,
105    #[serde(rename = "token")]
106    Token,
107    #[serde(rename = "totp")]
108    Totp,
109}
110
111/// A `user` object.
112#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
113#[builder(setter(strip_option))]
114pub struct PasswordUser<'a> {
115    /// A `domain` object
116    #[serde(skip_serializing_if = "Option::is_none")]
117    #[builder(default, setter(into))]
118    pub(crate) domain: Option<Domain<'a>>,
119
120    /// The ID of the user. Required if you do not specify the user name.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    #[builder(default, setter(into))]
123    pub(crate) id: Option<Cow<'a, str>>,
124
125    /// The user name. Required if you do not specify the ID of the user. If
126    /// you specify the user name, you must also specify the domain, by ID or
127    /// name.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    #[builder(default, setter(into))]
130    pub(crate) name: Option<Cow<'a, str>>,
131
132    /// User Password
133    #[serde(
134        serialize_with = "serialize_sensitive_optional_string",
135        skip_serializing_if = "Option::is_none"
136    )]
137    #[builder(default, setter(into))]
138    pub(crate) password: Option<SecretString>,
139}
140
141/// The `password` object, contains the authentication information.
142#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
143#[builder(setter(strip_option))]
144pub struct Password<'a> {
145    /// A `user` object.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    #[builder(default, setter(into))]
148    pub(crate) user: Option<PasswordUser<'a>>,
149}
150
151/// A `token` object
152#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
153#[builder(setter(strip_option))]
154pub struct Token {
155    /// Authorization Token value
156    #[serde(serialize_with = "serialize_sensitive_string")]
157    #[builder(setter(into))]
158    pub(crate) id: SecretString,
159}
160
161#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
162#[builder(setter(strip_option))]
163pub struct TotpUser<'a> {
164    /// A `domain` object
165    #[serde(skip_serializing_if = "Option::is_none")]
166    #[builder(default, setter(into))]
167    pub(crate) domain: Option<Domain<'a>>,
168
169    /// The user ID
170    #[serde(skip_serializing_if = "Option::is_none")]
171    #[builder(default, setter(into))]
172    pub(crate) id: Option<Cow<'a, str>>,
173
174    /// The user name
175    #[serde(skip_serializing_if = "Option::is_none")]
176    #[builder(default, setter(into))]
177    pub(crate) name: Option<Cow<'a, str>>,
178
179    /// MFA passcode
180    #[serde(serialize_with = "serialize_sensitive_string")]
181    #[builder(setter(into))]
182    pub(crate) passcode: SecretString,
183}
184
185/// Multi Factor Authentication information
186#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
187#[builder(setter(strip_option))]
188pub struct Totp<'a> {
189    #[serde()]
190    #[builder(setter(into))]
191    pub(crate) user: TotpUser<'a>,
192}
193
194/// An `identity` object.
195#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
196#[builder(setter(strip_option))]
197pub struct Identity<'a> {
198    /// An application credential object.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    #[builder(default, setter(into))]
201    pub(crate) application_credential: Option<ApplicationCredential<'a>>,
202
203    /// The authentication method. For password authentication, specify
204    /// `password`.
205    #[serde()]
206    #[builder(setter(into))]
207    pub(crate) methods: Vec<Methods>,
208
209    /// The `password` object, contains the authentication information.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    #[builder(default, setter(into))]
212    pub(crate) password: Option<Password<'a>>,
213
214    /// A `token` object
215    #[serde(skip_serializing_if = "Option::is_none")]
216    #[builder(default, setter(into))]
217    pub(crate) token: Option<Token>,
218
219    /// Multi Factor Authentication information
220    #[serde(skip_serializing_if = "Option::is_none")]
221    #[builder(default, setter(into))]
222    pub(crate) totp: Option<Totp<'a>>,
223}
224
225#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
226#[builder(setter(strip_option))]
227pub struct OsTrustTrust<'a> {
228    #[serde(skip_serializing_if = "Option::is_none")]
229    #[builder(default, setter(into))]
230    pub(crate) id: Option<Cow<'a, str>>,
231}
232
233#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
234#[builder(setter(strip_option))]
235pub struct ScopeDomain<'a> {
236    /// Domain id
237    #[serde(skip_serializing_if = "Option::is_none")]
238    #[builder(default, setter(into))]
239    pub(crate) id: Option<Cow<'a, str>>,
240
241    /// Domain name
242    #[serde(skip_serializing_if = "Option::is_none")]
243    #[builder(default, setter(into))]
244    pub(crate) name: Option<Cow<'a, str>>,
245}
246
247#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
248#[builder(setter(strip_option))]
249pub struct ProjectDomain<'a> {
250    /// Project domain Id
251    #[serde(skip_serializing_if = "Option::is_none")]
252    #[builder(default, setter(into))]
253    pub(crate) id: Option<Cow<'a, str>>,
254
255    /// Project domain Name
256    #[serde(skip_serializing_if = "Option::is_none")]
257    #[builder(default, setter(into))]
258    pub(crate) name: Option<Cow<'a, str>>,
259}
260
261#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
262#[builder(setter(strip_option))]
263pub struct Project<'a> {
264    #[serde(skip_serializing_if = "Option::is_none")]
265    #[builder(default, setter(into))]
266    pub(crate) domain: Option<ProjectDomain<'a>>,
267
268    /// Project Id
269    #[serde(skip_serializing_if = "Option::is_none")]
270    #[builder(default, setter(into))]
271    pub(crate) id: Option<Cow<'a, str>>,
272
273    /// Project Name
274    #[serde(skip_serializing_if = "Option::is_none")]
275    #[builder(default, setter(into))]
276    pub(crate) name: Option<Cow<'a, str>>,
277}
278
279#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
280#[builder(setter(strip_option))]
281pub struct System {
282    #[serde(skip_serializing_if = "Option::is_none")]
283    #[builder(default, setter(into))]
284    pub(crate) all: Option<bool>,
285}
286
287/// The authorization scope, including the system (Since v3.10), a project, or
288/// a domain (Since v3.4). If multiple scopes are specified in the same request
289/// (e.g. project and domain or domain and system) an HTTP 400 Bad Request will
290/// be returned, as a token cannot be simultaneously scoped to multiple
291/// authorization targets. An ID is sufficient to uniquely identify a project
292/// but if a project is specified by name, then the domain of the project must
293/// also be specified in order to uniquely identify the project by name. A
294/// domain scope may be specified by either the domain’s ID or name with
295/// equivalent results.
296#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
297#[builder(setter(strip_option))]
298pub struct Scope<'a> {
299    #[serde(skip_serializing_if = "Option::is_none")]
300    #[builder(default, setter(into))]
301    pub(crate) domain: Option<ScopeDomain<'a>>,
302
303    #[serde(rename = "OS-TRUST:trust", skip_serializing_if = "Option::is_none")]
304    #[builder(default, setter(into))]
305    pub(crate) os_trust_trust: Option<OsTrustTrust<'a>>,
306
307    #[serde(skip_serializing_if = "Option::is_none")]
308    #[builder(default, setter(into))]
309    pub(crate) project: Option<Project<'a>>,
310
311    #[serde(skip_serializing_if = "Option::is_none")]
312    #[builder(default, setter(into))]
313    pub(crate) system: Option<System>,
314}
315
316/// An `auth` object.
317#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
318#[builder(setter(strip_option))]
319pub struct Auth<'a> {
320    /// An `identity` object.
321    #[serde()]
322    #[builder(setter(into))]
323    pub(crate) identity: Identity<'a>,
324
325    /// The authorization scope, including the system (Since v3.10), a project,
326    /// or a domain (Since v3.4). If multiple scopes are specified in the same
327    /// request (e.g. project and domain or domain and system) an HTTP 400 Bad
328    /// Request will be returned, as a token cannot be simultaneously scoped to
329    /// multiple authorization targets. An ID is sufficient to uniquely
330    /// identify a project but if a project is specified by name, then the
331    /// domain of the project must also be specified in order to uniquely
332    /// identify the project by name. A domain scope may be specified by either
333    /// the domain’s ID or name with equivalent results.
334    #[serde(skip_serializing_if = "Option::is_none")]
335    #[builder(default, setter(into))]
336    pub(crate) scope: Option<Scope<'a>>,
337}
338
339#[derive(Builder, Debug, Clone)]
340#[builder(setter(strip_option))]
341pub struct Request<'a> {
342    /// An `auth` object.
343    #[builder(setter(into))]
344    pub(crate) auth: Auth<'a>,
345
346    #[builder(setter(name = "_headers"), default, private)]
347    _headers: Option<HeaderMap>,
348}
349impl<'a> Request<'a> {
350    /// Create a builder for the endpoint.
351    pub fn builder() -> RequestBuilder<'a> {
352        RequestBuilder::default()
353    }
354}
355
356impl<'a> RequestBuilder<'a> {
357    /// Add a single header to the Saml2.
358    pub fn header<K, V>(&mut self, header_name: K, header_value: V) -> &mut Self
359    where
360        K: Into<HeaderName>,
361        V: Into<HeaderValue>,
362    {
363        self._headers
364            .get_or_insert(None)
365            .get_or_insert_with(HeaderMap::new)
366            .insert(header_name.into(), header_value.into());
367        self
368    }
369
370    /// Add multiple headers.
371    pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
372    where
373        I: Iterator<Item = T>,
374        T: Into<(Option<HeaderName>, HeaderValue)>,
375    {
376        self._headers
377            .get_or_insert(None)
378            .get_or_insert_with(HeaderMap::new)
379            .extend(iter.map(Into::into));
380        self
381    }
382}
383
384impl RestEndpoint for Request<'_> {
385    fn method(&self) -> http::Method {
386        http::Method::POST
387    }
388
389    fn endpoint(&self) -> Cow<'static, str> {
390        "auth/OS-FEDERATION/saml2".to_string().into()
391    }
392
393    fn parameters(&self) -> QueryParams<'_> {
394        QueryParams::default()
395    }
396
397    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
398        let mut params = JsonBodyParams::default();
399
400        params.push("auth", serde_json::to_value(&self.auth)?);
401
402        params.into_body()
403    }
404
405    fn service_type(&self) -> ServiceType {
406        ServiceType::Identity
407    }
408
409    fn response_key(&self) -> Option<Cow<'static, str>> {
410        None
411    }
412
413    /// Returns headers to be set into the request
414    fn request_headers(&self) -> Option<&HeaderMap> {
415        self._headers.as_ref()
416    }
417
418    /// Returns required API version
419    fn api_version(&self) -> Option<ApiVersion> {
420        Some(ApiVersion::new(3, 0))
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use super::*;
427    use http::{HeaderName, HeaderValue};
428    use httpmock::MockServer;
429    #[cfg(feature = "sync")]
430    use openstack_sdk_core::api::Query;
431    use openstack_sdk_core::test::client::FakeOpenStackClient;
432    use openstack_sdk_core::types::ServiceType;
433    use serde_json::json;
434
435    #[test]
436    fn test_service_type() {
437        assert_eq!(
438            Request::builder()
439                .auth(
440                    AuthBuilder::default()
441                        .identity(
442                            IdentityBuilder::default()
443                                .methods(Vec::from([Methods::ApplicationCredential]))
444                                .build()
445                                .unwrap()
446                        )
447                        .build()
448                        .unwrap()
449                )
450                .build()
451                .unwrap()
452                .service_type(),
453            ServiceType::Identity
454        );
455    }
456
457    #[test]
458    fn test_response_key() {
459        assert!(
460            Request::builder()
461                .auth(
462                    AuthBuilder::default()
463                        .identity(
464                            IdentityBuilder::default()
465                                .methods(Vec::from([Methods::ApplicationCredential]))
466                                .build()
467                                .unwrap()
468                        )
469                        .build()
470                        .unwrap()
471                )
472                .build()
473                .unwrap()
474                .response_key()
475                .is_none()
476        )
477    }
478
479    #[cfg(feature = "sync")]
480    #[test]
481    fn endpoint() {
482        let server = MockServer::start();
483        let client = FakeOpenStackClient::new(server.base_url());
484        let mock = server.mock(|when, then| {
485            when.method(httpmock::Method::POST)
486                .path("/auth/OS-FEDERATION/saml2".to_string());
487
488            then.status(200)
489                .header("content-type", "application/json")
490                .json_body(json!({ "dummy": {} }));
491        });
492
493        let endpoint = Request::builder()
494            .auth(
495                AuthBuilder::default()
496                    .identity(
497                        IdentityBuilder::default()
498                            .methods(Vec::from([Methods::ApplicationCredential]))
499                            .build()
500                            .unwrap(),
501                    )
502                    .build()
503                    .unwrap(),
504            )
505            .build()
506            .unwrap();
507        let _: serde_json::Value = endpoint.query(&client).unwrap();
508        mock.assert();
509    }
510
511    #[cfg(feature = "sync")]
512    #[test]
513    fn endpoint_headers() {
514        let server = MockServer::start();
515        let client = FakeOpenStackClient::new(server.base_url());
516        let mock = server.mock(|when, then| {
517            when.method(httpmock::Method::POST)
518                .path("/auth/OS-FEDERATION/saml2".to_string())
519                .header("foo", "bar")
520                .header("not_foo", "not_bar");
521            then.status(200)
522                .header("content-type", "application/json")
523                .json_body(json!({ "dummy": {} }));
524        });
525
526        let endpoint = Request::builder()
527            .auth(
528                AuthBuilder::default()
529                    .identity(
530                        IdentityBuilder::default()
531                            .methods(Vec::from([Methods::ApplicationCredential]))
532                            .build()
533                            .unwrap(),
534                    )
535                    .build()
536                    .unwrap(),
537            )
538            .headers(
539                [(
540                    Some(HeaderName::from_static("foo")),
541                    HeaderValue::from_static("bar"),
542                )]
543                .into_iter(),
544            )
545            .header(
546                HeaderName::from_static("not_foo"),
547                HeaderValue::from_static("not_bar"),
548            )
549            .build()
550            .unwrap();
551        let _: serde_json::Value = endpoint.query(&client).unwrap();
552        mock.assert();
553    }
554}