Skip to main content

openstack_sdk_identity/v4/user/passkey/
register_finish.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
18use derive_builder::Builder;
19use http::{HeaderMap, HeaderName, HeaderValue};
20
21use openstack_sdk_core::api::rest_endpoint_prelude::*;
22
23use serde::Deserialize;
24use serde::Serialize;
25use std::borrow::Cow;
26
27/// <https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension>.
28#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
29#[builder(setter(strip_option))]
30pub struct CredProps {
31    /// A user agent supplied hint that this credential may have created a
32    /// resident key. It is returned from the user agent, not the authenticator
33    /// meaning that this is an unreliable signal.
34    ///
35    /// Note that this extension is UNSIGNED and may have been altered by page
36    /// javascript.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[builder(default, setter(into))]
39    pub(crate) rk: Option<Option<bool>>,
40}
41
42#[derive(Debug, Deserialize, Clone, Serialize)]
43pub enum CredProtect {
44    #[serde(rename = "Optional")]
45    Optional,
46    #[serde(rename = "OptionalWithCredentialIDList")]
47    Optionalwithcredentialidlist,
48    #[serde(rename = "Required")]
49    Required,
50}
51
52/// <https://w3c.github.io/webauthn/#dictdef-authenticationextensionsclientoutputs>
53/// The default option here for Options are None, so it can be derived.
54#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
55#[builder(setter(strip_option))]
56pub struct Extensions {
57    /// Indicates whether the client used the provided appid extension.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    #[builder(default, setter(into))]
60    pub(crate) appid: Option<bool>,
61
62    /// <https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension>.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    #[builder(default, setter(into))]
65    pub(crate) cred_props: Option<CredProps>,
66
67    /// Valid credential protection policies.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    #[builder(default)]
70    pub(crate) cred_protect: Option<CredProtect>,
71
72    /// Indicates if the client successfully applied a HMAC Secret.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    #[builder(default, setter(into))]
75    pub(crate) hmac_secret: Option<bool>,
76
77    /// Indicates the current minimum PIN length.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[builder(default, setter(into))]
80    pub(crate) min_pin_length: Option<u32>,
81}
82
83#[derive(Debug, Deserialize, Clone, Serialize)]
84pub enum Transports {
85    #[serde(rename = "Ble")]
86    Ble,
87    #[serde(rename = "Hybrid")]
88    Hybrid,
89    #[serde(rename = "Internal")]
90    Internal,
91    #[serde(rename = "Nfc")]
92    Nfc,
93    #[serde(rename = "Test")]
94    Test,
95    #[serde(rename = "Unknown")]
96    Unknown,
97    #[serde(rename = "Usb")]
98    Usb,
99}
100
101/// <https://w3c.github.io/webauthn/#authenticatorattestationresponse>.
102#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
103#[builder(setter(strip_option))]
104pub struct Response<'a> {
105    /// <https://w3c.github.io/webauthn/#dom-authenticatorattestationresponse-attestationobject>.
106    #[serde()]
107    #[builder(setter(into))]
108    pub(crate) attestation_object: Cow<'a, str>,
109
110    /// <https://w3c.github.io/webauthn/#dom-authenticatorresponse-clientdatajson>.
111    #[serde()]
112    #[builder(setter(into))]
113    pub(crate) client_data_json: Cow<'a, str>,
114
115    /// <https://w3c.github.io/webauthn/#dom-authenticatorattestationresponse-gettransports>.
116    #[serde(skip_serializing_if = "Option::is_none")]
117    #[builder(default, setter(into))]
118    pub(crate) transports: Option<Vec<Transports>>,
119}
120
121#[derive(Builder, Debug, Clone)]
122#[builder(setter(strip_option))]
123pub struct Request<'a> {
124    /// Optional credential description.
125    #[builder(default, setter(into))]
126    pub(crate) description: Option<Cow<'a, str>>,
127
128    /// <https://w3c.github.io/webauthn/#dictdef-authenticationextensionsclientoutputs>
129    /// The default option here for Options are None, so it can be derived.
130    #[builder(setter(into))]
131    pub(crate) extensions: Extensions,
132
133    /// The id of the PublicKey credential, likely in base64.
134    ///
135    /// This is NEVER actually used in a real registration, because the true
136    /// credential ID is taken from the attestation data.
137    #[builder(setter(into))]
138    pub(crate) id: Cow<'a, str>,
139
140    /// The id of the credential, as binary.
141    ///
142    /// This is NEVER actually used in a real registration, because the true
143    /// credential ID is taken from the attestation data.
144    #[builder(setter(into))]
145    pub(crate) raw_id: Cow<'a, str>,
146
147    /// <https://w3c.github.io/webauthn/#authenticatorattestationresponse>.
148    #[builder(setter(into))]
149    pub(crate) response: Response<'a>,
150
151    /// The type of credential.
152    #[builder(setter(into))]
153    pub(crate) type_: Cow<'a, str>,
154
155    /// The ID of the user.
156    #[builder(default, setter(into))]
157    user_id: Cow<'a, str>,
158
159    #[builder(setter(name = "_headers"), default, private)]
160    _headers: Option<HeaderMap>,
161}
162impl<'a> Request<'a> {
163    /// Create a builder for the endpoint.
164    pub fn builder() -> RequestBuilder<'a> {
165        RequestBuilder::default()
166    }
167}
168
169impl<'a> RequestBuilder<'a> {
170    /// Add a single header to the Register_Finish.
171    pub fn header<K, V>(&mut self, header_name: K, header_value: V) -> &mut Self
172    where
173        K: Into<HeaderName>,
174        V: Into<HeaderValue>,
175    {
176        self._headers
177            .get_or_insert(None)
178            .get_or_insert_with(HeaderMap::new)
179            .insert(header_name.into(), header_value.into());
180        self
181    }
182
183    /// Add multiple headers.
184    pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
185    where
186        I: Iterator<Item = T>,
187        T: Into<(Option<HeaderName>, HeaderValue)>,
188    {
189        self._headers
190            .get_or_insert(None)
191            .get_or_insert_with(HeaderMap::new)
192            .extend(iter.map(Into::into));
193        self
194    }
195}
196
197impl RestEndpoint for Request<'_> {
198    fn method(&self) -> http::Method {
199        http::Method::POST
200    }
201
202    fn endpoint(&self) -> Cow<'static, str> {
203        format!(
204            "users/{user_id}/passkeys/register_finish",
205            user_id = self.user_id.as_ref(),
206        )
207        .into()
208    }
209
210    fn parameters(&self) -> QueryParams<'_> {
211        QueryParams::default()
212    }
213
214    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
215        let mut params = JsonBodyParams::default();
216
217        if let Some(val) = &self.description {
218            params.push("description", serde_json::to_value(val)?);
219        }
220        params.push("extensions", serde_json::to_value(&self.extensions)?);
221        params.push("id", serde_json::to_value(&self.id)?);
222        params.push("raw_id", serde_json::to_value(&self.raw_id)?);
223        params.push("response", serde_json::to_value(&self.response)?);
224        params.push("type_", serde_json::to_value(&self.type_)?);
225
226        params.into_body()
227    }
228
229    fn service_type(&self) -> ServiceType {
230        ServiceType::Identity
231    }
232
233    fn response_key(&self) -> Option<Cow<'static, str>> {
234        Some("passkey".into())
235    }
236
237    /// Returns headers to be set into the request
238    fn request_headers(&self) -> Option<&HeaderMap> {
239        self._headers.as_ref()
240    }
241
242    /// Returns required API version
243    fn api_version(&self) -> Option<ApiVersion> {
244        Some(ApiVersion::new(4, 0))
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use http::{HeaderName, HeaderValue};
252    use httpmock::MockServer;
253    #[cfg(feature = "sync")]
254    use openstack_sdk_core::api::Query;
255    use openstack_sdk_core::test::client::FakeOpenStackClient;
256    use openstack_sdk_core::types::ServiceType;
257    use serde_json::json;
258
259    #[test]
260    fn test_service_type() {
261        assert_eq!(
262            Request::builder()
263                .extensions(ExtensionsBuilder::default().build().unwrap())
264                .id("foo")
265                .raw_id("foo")
266                .response(
267                    ResponseBuilder::default()
268                        .attestation_object("foo")
269                        .client_data_json("foo")
270                        .build()
271                        .unwrap()
272                )
273                .type_("foo")
274                .build()
275                .unwrap()
276                .service_type(),
277            ServiceType::Identity
278        );
279    }
280
281    #[test]
282    fn test_response_key() {
283        assert_eq!(
284            Request::builder()
285                .extensions(ExtensionsBuilder::default().build().unwrap())
286                .id("foo")
287                .raw_id("foo")
288                .response(
289                    ResponseBuilder::default()
290                        .attestation_object("foo")
291                        .client_data_json("foo")
292                        .build()
293                        .unwrap()
294                )
295                .type_("foo")
296                .build()
297                .unwrap()
298                .response_key()
299                .unwrap(),
300            "passkey"
301        );
302    }
303
304    #[cfg(feature = "sync")]
305    #[test]
306    fn endpoint() {
307        let server = MockServer::start();
308        let client = FakeOpenStackClient::new(server.base_url());
309        let mock = server.mock(|when, then| {
310            when.method(httpmock::Method::POST).path(format!(
311                "/users/{user_id}/passkeys/register_finish",
312                user_id = "user_id",
313            ));
314
315            then.status(200)
316                .header("content-type", "application/json")
317                .json_body(json!({ "passkey": {} }));
318        });
319
320        let endpoint = Request::builder()
321            .user_id("user_id")
322            .extensions(ExtensionsBuilder::default().build().unwrap())
323            .id("foo")
324            .raw_id("foo")
325            .response(
326                ResponseBuilder::default()
327                    .attestation_object("foo")
328                    .client_data_json("foo")
329                    .build()
330                    .unwrap(),
331            )
332            .type_("foo")
333            .build()
334            .unwrap();
335        let _: serde_json::Value = endpoint.query(&client).unwrap();
336        mock.assert();
337    }
338
339    #[cfg(feature = "sync")]
340    #[test]
341    fn endpoint_headers() {
342        let server = MockServer::start();
343        let client = FakeOpenStackClient::new(server.base_url());
344        let mock = server.mock(|when, then| {
345            when.method(httpmock::Method::POST)
346                .path(format!(
347                    "/users/{user_id}/passkeys/register_finish",
348                    user_id = "user_id",
349                ))
350                .header("foo", "bar")
351                .header("not_foo", "not_bar");
352            then.status(200)
353                .header("content-type", "application/json")
354                .json_body(json!({ "passkey": {} }));
355        });
356
357        let endpoint = Request::builder()
358            .user_id("user_id")
359            .extensions(ExtensionsBuilder::default().build().unwrap())
360            .id("foo")
361            .raw_id("foo")
362            .response(
363                ResponseBuilder::default()
364                    .attestation_object("foo")
365                    .client_data_json("foo")
366                    .build()
367                    .unwrap(),
368            )
369            .type_("foo")
370            .headers(
371                [(
372                    Some(HeaderName::from_static("foo")),
373                    HeaderValue::from_static("bar"),
374                )]
375                .into_iter(),
376            )
377            .header(
378                HeaderName::from_static("not_foo"),
379                HeaderValue::from_static("not_bar"),
380            )
381            .build()
382            .unwrap();
383        let _: serde_json::Value = endpoint.query(&client).unwrap();
384        mock.assert();
385    }
386}