utoipa/openapi/security.rs
1//! Implements [OpenAPI Security Schema][security] types.
2//!
3//! Refer to [`SecurityScheme`] for usage and more details.
4//!
5//! [security]: https://spec.openapis.org/oas/latest.html#security-scheme-object
6use std::{collections::BTreeMap, iter};
7
8use serde::{Deserialize, Serialize};
9
10use super::{builder, extensions::Extensions, Deprecated};
11
12/// OpenAPI [security requirement][security] object.
13///
14/// Security requirement holds list of required [`SecurityScheme`] *names* and possible *scopes* required
15/// to execute the operation. They can be defined in [`#[utoipa::path(...)]`][path] or in `#[openapi(...)]`
16/// of [`OpenApi`][openapi].
17///
18/// Applying the security requirement to [`OpenApi`][openapi] will make it globally
19/// available to all operations. When applied to specific [`#[utoipa::path(...)]`][path] will only
20/// make the security requirements available for that operation. Only one of the requirements must be
21/// satisfied.
22///
23/// [security]: https://spec.openapis.org/oas/latest.html#security-requirement-object
24/// [path]: ../../attr.path.html
25/// [openapi]: ../../derive.OpenApi.html
26#[non_exhaustive]
27#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "debug", derive(Debug))]
29pub struct SecurityRequirement {
30 #[serde(flatten)]
31 value: BTreeMap<String, Vec<String>>,
32}
33
34impl SecurityRequirement {
35 /// Construct a new [`SecurityRequirement`].
36 ///
37 /// Accepts name for the security requirement which must match to the name of available [`SecurityScheme`].
38 /// Second parameter is [`IntoIterator`] of [`Into<String>`] scopes needed by the [`SecurityRequirement`].
39 /// Scopes must match to the ones defined in [`SecurityScheme`].
40 ///
41 /// # Examples
42 ///
43 /// Create new security requirement with scopes.
44 /// ```rust
45 /// # use utoipa::openapi::security::SecurityRequirement;
46 /// SecurityRequirement::new("api_oauth2_flow", ["edit:items", "read:items"]);
47 /// ```
48 ///
49 /// You can also create an empty security requirement with `Default::default()`.
50 /// ```rust
51 /// # use utoipa::openapi::security::SecurityRequirement;
52 /// SecurityRequirement::default();
53 /// ```
54 ///
55 /// If you have more than one name in the security requirement you can use
56 /// [`SecurityRequirement::add`].
57 pub fn new<N: Into<String>, S: IntoIterator<Item = I>, I: Into<String>>(
58 name: N,
59 scopes: S,
60 ) -> Self {
61 Self {
62 value: BTreeMap::from_iter(iter::once_with(|| {
63 (
64 Into::<String>::into(name),
65 scopes
66 .into_iter()
67 .map(|scope| Into::<String>::into(scope))
68 .collect::<Vec<_>>(),
69 )
70 })),
71 }
72 }
73
74 /// Allows to add multiple names to security requirement.
75 ///
76 /// Accepts name for the security requirement which must match to the name of available [`SecurityScheme`].
77 /// Second parameter is [`IntoIterator`] of [`Into<String>`] scopes needed by the [`SecurityRequirement`].
78 /// Scopes must match to the ones defined in [`SecurityScheme`].
79 ///
80 /// # Examples
81 ///
82 /// Make both API keys required:
83 /// ```rust
84 /// # use utoipa::openapi::security::{SecurityRequirement, HttpAuthScheme, HttpBuilder, SecurityScheme};
85 /// # use utoipa::{openapi, Modify, OpenApi};
86 /// # use serde::Serialize;
87 /// #[derive(Debug, Serialize)]
88 /// struct Foo;
89 ///
90 /// impl Modify for Foo {
91 /// fn modify(&self, openapi: &mut openapi::OpenApi) {
92 /// if let Some(schema) = openapi.components.as_mut() {
93 /// schema.add_security_scheme(
94 /// "api_key1",
95 /// SecurityScheme::Http(
96 /// HttpBuilder::new()
97 /// .scheme(HttpAuthScheme::Bearer)
98 /// .bearer_format("JWT")
99 /// .build(),
100 /// ),
101 /// );
102 /// schema.add_security_scheme(
103 /// "api_key2",
104 /// SecurityScheme::Http(
105 /// HttpBuilder::new()
106 /// .scheme(HttpAuthScheme::Bearer)
107 /// .bearer_format("JWT")
108 /// .build(),
109 /// ),
110 /// );
111 /// }
112 /// }
113 /// }
114 ///
115 /// #[derive(Default, OpenApi)]
116 /// #[openapi(
117 /// modifiers(&Foo),
118 /// security(
119 /// ("api_key1" = ["edit:items", "read:items"], "api_key2" = ["edit:items", "read:items"]),
120 /// )
121 /// )]
122 /// struct ApiDoc;
123 /// ```
124 pub fn add<N: Into<String>, S: IntoIterator<Item = I>, I: Into<String>>(
125 mut self,
126 name: N,
127 scopes: S,
128 ) -> Self {
129 self.value.insert(
130 Into::<String>::into(name),
131 scopes.into_iter().map(Into::<String>::into).collect(),
132 );
133
134 self
135 }
136}
137
138/// OpenAPI [security scheme][security] for path operations.
139///
140/// [security]: https://spec.openapis.org/oas/latest.html#security-scheme-object
141///
142/// # Examples
143///
144/// Create implicit oauth2 flow security schema for path operations.
145/// ```rust
146/// # use utoipa::openapi::security::{SecurityScheme, OAuth2, Implicit, Flow, Scopes};
147/// SecurityScheme::OAuth2(
148/// OAuth2::with_description([Flow::Implicit(
149/// Implicit::new(
150/// "https://localhost/auth/dialog",
151/// Scopes::from_iter([
152/// ("edit:items", "edit my items"),
153/// ("read:items", "read my items")
154/// ]),
155/// ),
156/// )], "my oauth2 flow")
157/// );
158/// ```
159///
160/// Create JWT header authentication.
161/// ```rust
162/// # use utoipa::openapi::security::{SecurityScheme, HttpAuthScheme, HttpBuilder};
163/// SecurityScheme::Http(
164/// HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build()
165/// );
166/// ```
167#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
168#[serde(tag = "type", rename_all = "camelCase")]
169#[cfg_attr(feature = "debug", derive(Debug))]
170pub enum SecurityScheme {
171 /// Oauth flow authentication.
172 #[serde(rename = "oauth2")]
173 OAuth2(OAuth2),
174 /// Api key authentication sent in *`header`*, *`cookie`* or *`query`*.
175 ApiKey(ApiKey),
176 /// Http authentication such as *`bearer`* or *`basic`*.
177 Http(Http),
178 /// Open id connect url to discover OAuth2 configuration values.
179 OpenIdConnect(OpenIdConnect),
180 /// Authentication is done via client side certificate.
181 ///
182 /// OpenApi 3.1 type
183 ///
184 /// # Examples
185 ///
186 /// Declare mutual TLS authentication with a description.
187 /// ```rust
188 /// # use utoipa::openapi::security::SecurityScheme;
189 /// SecurityScheme::MutualTls {
190 /// description: Some("Client certificate required".to_string()),
191 /// deprecated: None,
192 /// extensions: None,
193 /// };
194 /// ```
195 #[serde(rename = "mutualTLS")]
196 MutualTls {
197 /// Optional description explaining how the mutual TLS certificate should be obtained
198 /// and used. Description supports markdown syntax.
199 #[serde(skip_serializing_if = "Option::is_none")]
200 description: Option<String>,
201 /// Declares whether the security scheme is deprecated.
202 #[serde(skip_serializing_if = "Option::is_none")]
203 deprecated: Option<Deprecated>,
204 /// Optional extensions "x-something".
205 #[serde(skip_serializing_if = "Option::is_none", flatten)]
206 extensions: Option<Extensions>,
207 },
208}
209
210/// Api key authentication [`SecurityScheme`].
211#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
212#[serde(tag = "in", rename_all = "lowercase")]
213#[cfg_attr(feature = "debug", derive(Debug))]
214pub enum ApiKey {
215 /// Create api key which is placed in HTTP header.
216 Header(ApiKeyValue),
217 /// Create api key which is placed in query parameters.
218 Query(ApiKeyValue),
219 /// Create api key which is placed in cookie value.
220 Cookie(ApiKeyValue),
221}
222
223/// Value object for [`ApiKey`].
224#[non_exhaustive]
225#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
226#[cfg_attr(feature = "debug", derive(Debug))]
227pub struct ApiKeyValue {
228 /// Name of the [`ApiKey`] parameter.
229 pub name: String,
230
231 /// Description of the the [`ApiKey`] [`SecurityScheme`]. Supports markdown syntax.
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub description: Option<String>,
234
235 /// Declares whether the security scheme is deprecated.
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub deprecated: Option<Deprecated>,
238
239 /// Optional extensions "x-something".
240 #[serde(skip_serializing_if = "Option::is_none", flatten)]
241 pub extensions: Option<Extensions>,
242}
243
244impl ApiKeyValue {
245 /// Constructs new api key value.
246 ///
247 /// # Examples
248 ///
249 /// Create new api key security schema with name `api_key`.
250 /// ```rust
251 /// # use utoipa::openapi::security::ApiKeyValue;
252 /// let api_key = ApiKeyValue::new("api_key");
253 /// ```
254 pub fn new<S: Into<String>>(name: S) -> Self {
255 Self {
256 name: name.into(),
257 description: None,
258 deprecated: None,
259 extensions: Default::default(),
260 }
261 }
262
263 /// Construct a new api key with optional description supporting markdown syntax.
264 ///
265 /// # Examples
266 ///
267 /// Create new api key security schema with name `api_key` with description.
268 /// ```rust
269 /// # use utoipa::openapi::security::ApiKeyValue;
270 /// let api_key = ApiKeyValue::with_description("api_key", "my api_key token");
271 /// ```
272 pub fn with_description<S: Into<String>>(name: S, description: S) -> Self {
273 Self {
274 name: name.into(),
275 description: Some(description.into()),
276 deprecated: None,
277 extensions: Default::default(),
278 }
279 }
280
281 /// Add or change deprecated status.
282 ///
283 /// # Examples
284 ///
285 /// Mark an api key security scheme as deprecated.
286 /// ```rust
287 /// # use utoipa::openapi::security::ApiKeyValue;
288 /// # use utoipa::openapi::Deprecated;
289 /// ApiKeyValue::new("api_key").deprecated(Some(Deprecated::True));
290 /// ```
291 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
292 self.deprecated = deprecated;
293 self
294 }
295}
296
297builder! {
298 HttpBuilder;
299
300 /// Http authentication [`SecurityScheme`] builder.
301 ///
302 /// Methods can be chained to configure _bearer_format_ or to add _description_.
303 #[non_exhaustive]
304 #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
305 #[serde(rename_all = "camelCase")]
306 #[cfg_attr(feature = "debug", derive(Debug))]
307 pub struct Http {
308 /// Http authorization scheme in HTTP `Authorization` header value.
309 pub scheme: HttpAuthScheme,
310
311 /// Optional hint to client how the bearer token is formatted. Valid only with [`HttpAuthScheme::Bearer`].
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub bearer_format: Option<String>,
314
315 /// Optional description of [`Http`] [`SecurityScheme`] supporting markdown syntax.
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub description: Option<String>,
318
319 /// Declares whether the security scheme is deprecated.
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub deprecated: Option<Deprecated>,
322
323 /// Optional extensions "x-something".
324 #[serde(skip_serializing_if = "Option::is_none", flatten)]
325 pub extensions: Option<Extensions>,
326 }
327}
328
329impl Http {
330 /// Create new http authentication security schema.
331 ///
332 /// Accepts one argument which defines the scheme of the http authentication.
333 ///
334 /// # Examples
335 ///
336 /// Create http security schema with basic authentication.
337 /// ```rust
338 /// # use utoipa::openapi::security::{SecurityScheme, Http, HttpAuthScheme};
339 /// SecurityScheme::Http(Http::new(HttpAuthScheme::Basic));
340 /// ```
341 pub fn new(scheme: HttpAuthScheme) -> Self {
342 Self {
343 scheme,
344 bearer_format: None,
345 description: None,
346 deprecated: None,
347 extensions: Default::default(),
348 }
349 }
350}
351
352impl HttpBuilder {
353 /// Add or change http authentication scheme used.
354 ///
355 /// # Examples
356 ///
357 /// Create new [`Http`] [`SecurityScheme`] via [`HttpBuilder`].
358 /// ```rust
359 /// # use utoipa::openapi::security::{HttpBuilder, HttpAuthScheme};
360 /// let http = HttpBuilder::new().scheme(HttpAuthScheme::Basic).build();
361 /// ```
362 pub fn scheme(mut self, scheme: HttpAuthScheme) -> Self {
363 self.scheme = scheme;
364
365 self
366 }
367 /// Add or change informative bearer format for http security schema.
368 ///
369 /// This is only applicable to [`HttpAuthScheme::Bearer`].
370 ///
371 /// # Examples
372 ///
373 /// Add JTW bearer format for security schema.
374 /// ```rust
375 /// # use utoipa::openapi::security::{HttpBuilder, HttpAuthScheme};
376 /// HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build();
377 /// ```
378 pub fn bearer_format<S: Into<String>>(mut self, bearer_format: S) -> Self {
379 if self.scheme == HttpAuthScheme::Bearer {
380 self.bearer_format = Some(bearer_format.into());
381 }
382
383 self
384 }
385
386 /// Add or change optional description supporting markdown syntax.
387 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
388 self.description = description.map(|description| description.into());
389
390 self
391 }
392
393 /// Add or change deprecated status.
394 ///
395 /// # Examples
396 ///
397 /// Mark a bearer HTTP authentication scheme as deprecated.
398 /// ```rust
399 /// # use utoipa::openapi::security::{HttpBuilder, HttpAuthScheme};
400 /// # use utoipa::openapi::Deprecated;
401 /// HttpBuilder::new()
402 /// .scheme(HttpAuthScheme::Bearer)
403 /// .bearer_format("JWT")
404 /// .deprecated(Some(Deprecated::True))
405 /// .build();
406 /// ```
407 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
408 self.deprecated = deprecated;
409 self
410 }
411}
412
413/// Implements types according [RFC7235](https://datatracker.ietf.org/doc/html/rfc7235#section-5.1).
414///
415/// Types are maintained at <https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml>.
416#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
417#[cfg_attr(feature = "debug", derive(Debug))]
418#[serde(rename_all = "lowercase")]
419#[allow(missing_docs)]
420pub enum HttpAuthScheme {
421 Basic,
422 Bearer,
423 Digest,
424 Hoba,
425 Mutual,
426 Negotiate,
427 OAuth,
428 #[serde(rename = "scram-sha-1")]
429 ScramSha1,
430 #[serde(rename = "scram-sha-256")]
431 ScramSha256,
432 Vapid,
433}
434
435impl Default for HttpAuthScheme {
436 fn default() -> Self {
437 Self::Basic
438 }
439}
440
441/// Open id connect [`SecurityScheme`].
442#[non_exhaustive]
443#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
444#[serde(rename_all = "camelCase")]
445#[cfg_attr(feature = "debug", derive(Debug))]
446pub struct OpenIdConnect {
447 /// Url of the [`OpenIdConnect`] to discover OAuth2 connect values.
448 pub open_id_connect_url: String,
449
450 /// Description of [`OpenIdConnect`] [`SecurityScheme`] supporting markdown syntax.
451 #[serde(skip_serializing_if = "Option::is_none")]
452 pub description: Option<String>,
453
454 /// Declares whether the security scheme is deprecated.
455 #[serde(skip_serializing_if = "Option::is_none")]
456 pub deprecated: Option<Deprecated>,
457
458 /// Optional extensions "x-something".
459 #[serde(skip_serializing_if = "Option::is_none", flatten)]
460 pub extensions: Option<Extensions>,
461}
462
463impl OpenIdConnect {
464 /// Construct a new open id connect security schema.
465 ///
466 /// # Examples
467 ///
468 /// ```rust
469 /// # use utoipa::openapi::security::OpenIdConnect;
470 /// OpenIdConnect::new("https://localhost/openid");
471 /// ```
472 pub fn new<S: Into<String>>(open_id_connect_url: S) -> Self {
473 Self {
474 open_id_connect_url: open_id_connect_url.into(),
475 description: None,
476 deprecated: None,
477 extensions: Default::default(),
478 }
479 }
480
481 /// Construct a new [`OpenIdConnect`] [`SecurityScheme`] with optional description
482 /// supporting markdown syntax.
483 ///
484 /// # Examples
485 ///
486 /// ```rust
487 /// # use utoipa::openapi::security::OpenIdConnect;
488 /// OpenIdConnect::with_description("https://localhost/openid", "my pet api open id connect");
489 /// ```
490 pub fn with_description<S: Into<String>>(open_id_connect_url: S, description: S) -> Self {
491 Self {
492 open_id_connect_url: open_id_connect_url.into(),
493 description: Some(description.into()),
494 deprecated: None,
495 extensions: Default::default(),
496 }
497 }
498
499 /// Add or change deprecated status.
500 ///
501 /// # Examples
502 ///
503 /// Mark an OpenID Connect security scheme as deprecated.
504 /// ```rust
505 /// # use utoipa::openapi::security::OpenIdConnect;
506 /// # use utoipa::openapi::Deprecated;
507 /// OpenIdConnect::new("https://localhost/openid").deprecated(Some(Deprecated::True));
508 /// ```
509 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
510 self.deprecated = deprecated;
511 self
512 }
513}
514
515/// OAuth2 [`Flow`] configuration for [`SecurityScheme`].
516#[non_exhaustive]
517#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
518#[serde(rename_all = "camelCase")]
519#[cfg_attr(feature = "debug", derive(Debug))]
520pub struct OAuth2 {
521 /// Map of supported OAuth2 flows.
522 pub flows: BTreeMap<String, Flow>,
523
524 /// URL to OAuth2 authorization server metadata.
525 #[serde(skip_serializing_if = "Option::is_none")]
526 pub oauth2_metadata_url: Option<String>,
527
528 /// Optional description for the [`OAuth2`] [`Flow`] [`SecurityScheme`].
529 #[serde(skip_serializing_if = "Option::is_none")]
530 pub description: Option<String>,
531
532 /// Declares whether the security scheme is deprecated.
533 #[serde(skip_serializing_if = "Option::is_none")]
534 pub deprecated: Option<Deprecated>,
535
536 /// Optional extensions "x-something".
537 #[serde(skip_serializing_if = "Option::is_none", flatten)]
538 pub extensions: Option<Extensions>,
539}
540
541impl OAuth2 {
542 /// Construct a new OAuth2 security schema configuration object.
543 ///
544 /// Oauth flow accepts slice of [`Flow`] configuration objects and can be optionally provided with description.
545 ///
546 /// # Examples
547 ///
548 /// Create new OAuth2 flow with multiple authentication flows.
549 /// ```rust
550 /// # use utoipa::openapi::security::{OAuth2, Flow, Password, AuthorizationCode, Scopes};
551 /// OAuth2::new([Flow::Password(
552 /// Password::with_refresh_url(
553 /// "https://localhost/oauth/token",
554 /// Scopes::from_iter([
555 /// ("edit:items", "edit my items"),
556 /// ("read:items", "read my items")
557 /// ]),
558 /// "https://localhost/refresh/token"
559 /// )),
560 /// Flow::AuthorizationCode(
561 /// AuthorizationCode::new(
562 /// "https://localhost/authorization/token",
563 /// "https://localhost/token/url",
564 /// Scopes::from_iter([
565 /// ("edit:items", "edit my items"),
566 /// ("read:items", "read my items")
567 /// ])),
568 /// ),
569 /// ]);
570 /// ```
571 pub fn new<I: IntoIterator<Item = Flow>>(flows: I) -> Self {
572 Self {
573 flows: BTreeMap::from_iter(
574 flows
575 .into_iter()
576 .map(|auth_flow| (String::from(auth_flow.get_type_as_str()), auth_flow)),
577 ),
578 extensions: None,
579 oauth2_metadata_url: None,
580 description: None,
581 deprecated: None,
582 }
583 }
584
585 /// Construct a new OAuth2 flow with optional description supporting markdown syntax.
586 ///
587 /// # Examples
588 ///
589 /// Create new OAuth2 flow with multiple authentication flows with description.
590 /// ```rust
591 /// # use utoipa::openapi::security::{OAuth2, Flow, Password, AuthorizationCode, Scopes};
592 /// OAuth2::with_description([Flow::Password(
593 /// Password::with_refresh_url(
594 /// "https://localhost/oauth/token",
595 /// Scopes::from_iter([
596 /// ("edit:items", "edit my items"),
597 /// ("read:items", "read my items")
598 /// ]),
599 /// "https://localhost/refresh/token"
600 /// )),
601 /// Flow::AuthorizationCode(
602 /// AuthorizationCode::new(
603 /// "https://localhost/authorization/token",
604 /// "https://localhost/token/url",
605 /// Scopes::from_iter([
606 /// ("edit:items", "edit my items"),
607 /// ("read:items", "read my items")
608 /// ])
609 /// ),
610 /// ),
611 /// ], "my oauth2 flow");
612 /// ```
613 pub fn with_description<I: IntoIterator<Item = Flow>, S: Into<String>>(
614 flows: I,
615 description: S,
616 ) -> Self {
617 Self {
618 flows: BTreeMap::from_iter(
619 flows
620 .into_iter()
621 .map(|auth_flow| (String::from(auth_flow.get_type_as_str()), auth_flow)),
622 ),
623 extensions: None,
624 oauth2_metadata_url: None,
625 description: Some(description.into()),
626 deprecated: None,
627 }
628 }
629
630 /// Add OAuth2 authorization server metadata URL.
631 ///
632 /// # Examples
633 ///
634 /// Add authorization server metadata URL to an existing [`OAuth2`] security scheme.
635 /// ```rust
636 /// # use utoipa::openapi::security::{OAuth2, Flow, Password, Scopes};
637 /// OAuth2::new([Flow::Password(
638 /// Password::new(
639 /// "https://localhost/oauth/token",
640 /// Scopes::from_iter([("edit:items", "edit my items")]),
641 /// ),
642 /// )])
643 /// .with_metadata_url("https://localhost/.well-known/oauth-authorization-server");
644 /// ```
645 pub fn with_metadata_url<S: Into<String>>(mut self, oauth2_metadata_url: S) -> Self {
646 self.oauth2_metadata_url = Some(oauth2_metadata_url.into());
647 self
648 }
649
650 /// Add or change deprecated status.
651 ///
652 /// Declaring a security scheme deprecated signals to API consumers that they should migrate
653 /// away from it. This is an OpenAPI 3.2 addition available on all [`SecurityScheme`] variants.
654 ///
655 /// # Examples
656 ///
657 /// Mark an [`OAuth2`] security scheme as deprecated.
658 /// ```rust
659 /// # use utoipa::openapi::security::{OAuth2, Flow, Password, Scopes};
660 /// # use utoipa::openapi::Deprecated;
661 /// OAuth2::new([Flow::Password(
662 /// Password::new(
663 /// "https://localhost/oauth/token",
664 /// Scopes::from_iter([("edit:items", "edit my items")]),
665 /// ),
666 /// )])
667 /// .deprecated(Some(Deprecated::True));
668 /// ```
669 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
670 self.deprecated = deprecated;
671 self
672 }
673}
674
675/// [`OAuth2`] flow configuration object.
676///
677/// See more details at <https://spec.openapis.org/oas/latest.html#oauth-flows-object>.
678#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
679#[serde(untagged)]
680#[cfg_attr(feature = "debug", derive(Debug))]
681pub enum Flow {
682 /// Define implicit [`Flow`] type. See [`Implicit::new`] for usage details.
683 ///
684 /// Soon to be deprecated by <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics>.
685 Implicit(Implicit),
686 /// Define password [`Flow`] type. See [`Password::new`] for usage details.
687 Password(Password),
688 /// Define client credentials [`Flow`] type. See [`ClientCredentials::new`] for usage details.
689 ClientCredentials(ClientCredentials),
690 /// Define authorization code [`Flow`] type. See [`AuthorizationCode::new`] for usage details.
691 AuthorizationCode(AuthorizationCode),
692 /// Define device authorization [`Flow`] type. See [`DeviceAuthorization::new`] for usage details.
693 DeviceAuthorization(DeviceAuthorization),
694}
695
696impl Flow {
697 fn get_type_as_str(&self) -> &str {
698 match self {
699 Self::Implicit(_) => "implicit",
700 Self::Password(_) => "password",
701 Self::ClientCredentials(_) => "clientCredentials",
702 Self::AuthorizationCode(_) => "authorizationCode",
703 Self::DeviceAuthorization(_) => "deviceAuthorization",
704 }
705 }
706}
707
708/// Implicit [`Flow`] configuration for [`OAuth2`].
709#[non_exhaustive]
710#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
711#[serde(rename_all = "camelCase")]
712#[cfg_attr(feature = "debug", derive(Debug))]
713pub struct Implicit {
714 /// Authorization token url for the flow.
715 pub authorization_url: String,
716
717 /// Optional refresh token url for the flow.
718 #[serde(skip_serializing_if = "Option::is_none")]
719 pub refresh_url: Option<String>,
720
721 /// Scopes required by the flow.
722 #[serde(flatten)]
723 pub scopes: Scopes,
724
725 /// Optional extensions "x-something".
726 #[serde(skip_serializing_if = "Option::is_none", flatten)]
727 pub extensions: Option<Extensions>,
728}
729
730impl Implicit {
731 /// Construct a new implicit oauth2 flow.
732 ///
733 /// Accepts two arguments: one which is authorization url and second map of scopes. Scopes can
734 /// also be an empty map.
735 ///
736 /// # Examples
737 ///
738 /// Create new implicit flow with scopes.
739 /// ```rust
740 /// # use utoipa::openapi::security::{Implicit, Scopes};
741 /// Implicit::new(
742 /// "https://localhost/auth/dialog",
743 /// Scopes::from_iter([
744 /// ("edit:items", "edit my items"),
745 /// ("read:items", "read my items")
746 /// ]),
747 /// );
748 /// ```
749 ///
750 /// Create new implicit flow without any scopes.
751 /// ```rust
752 /// # use utoipa::openapi::security::{Implicit, Scopes};
753 /// Implicit::new(
754 /// "https://localhost/auth/dialog",
755 /// Scopes::new(),
756 /// );
757 /// ```
758 pub fn new<S: Into<String>>(authorization_url: S, scopes: Scopes) -> Self {
759 Self {
760 authorization_url: authorization_url.into(),
761 refresh_url: None,
762 scopes,
763 extensions: Default::default(),
764 }
765 }
766
767 /// Construct a new implicit oauth2 flow with refresh url for getting refresh tokens.
768 ///
769 /// This is essentially same as [`Implicit::new`] but allows defining `refresh_url` for the [`Implicit`]
770 /// oauth2 flow.
771 ///
772 /// # Examples
773 ///
774 /// Create a new implicit oauth2 flow with refresh token.
775 /// ```rust
776 /// # use utoipa::openapi::security::{Implicit, Scopes};
777 /// Implicit::with_refresh_url(
778 /// "https://localhost/auth/dialog",
779 /// Scopes::new(),
780 /// "https://localhost/refresh-token"
781 /// );
782 /// ```
783 pub fn with_refresh_url<S: Into<String>>(
784 authorization_url: S,
785 scopes: Scopes,
786 refresh_url: S,
787 ) -> Self {
788 Self {
789 authorization_url: authorization_url.into(),
790 refresh_url: Some(refresh_url.into()),
791 scopes,
792 extensions: Default::default(),
793 }
794 }
795}
796
797/// Authorization code [`Flow`] configuration for [`OAuth2`].
798#[non_exhaustive]
799#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
800#[serde(rename_all = "camelCase")]
801#[cfg_attr(feature = "debug", derive(Debug))]
802pub struct AuthorizationCode {
803 /// Url for authorization token.
804 pub authorization_url: String,
805 /// Token url for the flow.
806 pub token_url: String,
807
808 /// Optional refresh token url for the flow.
809 #[serde(skip_serializing_if = "Option::is_none")]
810 pub refresh_url: Option<String>,
811
812 /// Scopes required by the flow.
813 #[serde(flatten)]
814 pub scopes: Scopes,
815
816 /// Optional extensions "x-something".
817 #[serde(skip_serializing_if = "Option::is_none", flatten)]
818 pub extensions: Option<Extensions>,
819}
820
821impl AuthorizationCode {
822 /// Construct a new authorization code oauth flow.
823 ///
824 /// Accepts three arguments: one which is authorization url, two a token url and
825 /// three a map of scopes for oauth flow.
826 ///
827 /// # Examples
828 ///
829 /// Create new authorization code flow with scopes.
830 /// ```rust
831 /// # use utoipa::openapi::security::{AuthorizationCode, Scopes};
832 /// AuthorizationCode::new(
833 /// "https://localhost/auth/dialog",
834 /// "https://localhost/token",
835 /// Scopes::from_iter([
836 /// ("edit:items", "edit my items"),
837 /// ("read:items", "read my items")
838 /// ]),
839 /// );
840 /// ```
841 ///
842 /// Create new authorization code flow without any scopes.
843 /// ```rust
844 /// # use utoipa::openapi::security::{AuthorizationCode, Scopes};
845 /// AuthorizationCode::new(
846 /// "https://localhost/auth/dialog",
847 /// "https://localhost/token",
848 /// Scopes::new(),
849 /// );
850 /// ```
851 pub fn new<A: Into<String>, T: Into<String>>(
852 authorization_url: A,
853 token_url: T,
854 scopes: Scopes,
855 ) -> Self {
856 Self {
857 authorization_url: authorization_url.into(),
858 token_url: token_url.into(),
859 refresh_url: None,
860 scopes,
861 extensions: Default::default(),
862 }
863 }
864
865 /// Construct a new [`AuthorizationCode`] OAuth2 flow with additional refresh token url.
866 ///
867 /// This is essentially same as [`AuthorizationCode::new`] but allows defining extra parameter `refresh_url`
868 /// for fetching refresh token.
869 ///
870 /// # Examples
871 ///
872 /// Create [`AuthorizationCode`] OAuth2 flow with refresh url.
873 /// ```rust
874 /// # use utoipa::openapi::security::{AuthorizationCode, Scopes};
875 /// AuthorizationCode::with_refresh_url(
876 /// "https://localhost/auth/dialog",
877 /// "https://localhost/token",
878 /// Scopes::new(),
879 /// "https://localhost/refresh-token"
880 /// );
881 /// ```
882 pub fn with_refresh_url<S: Into<String>>(
883 authorization_url: S,
884 token_url: S,
885 scopes: Scopes,
886 refresh_url: S,
887 ) -> Self {
888 Self {
889 authorization_url: authorization_url.into(),
890 token_url: token_url.into(),
891 refresh_url: Some(refresh_url.into()),
892 scopes,
893 extensions: Default::default(),
894 }
895 }
896}
897
898/// Device authorization [`Flow`] configuration for [`OAuth2`].
899#[non_exhaustive]
900#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
901#[serde(rename_all = "camelCase")]
902#[cfg_attr(feature = "debug", derive(Debug))]
903pub struct DeviceAuthorization {
904 /// Device authorization endpoint URL for the flow.
905 pub device_authorization_url: String,
906
907 /// Token URL for the flow.
908 pub token_url: String,
909
910 /// Optional refresh token URL for the flow.
911 #[serde(skip_serializing_if = "Option::is_none")]
912 pub refresh_url: Option<String>,
913
914 /// Scopes required by the flow.
915 #[serde(flatten)]
916 pub scopes: Scopes,
917
918 /// Optional extensions "x-something".
919 #[serde(skip_serializing_if = "Option::is_none", flatten)]
920 pub extensions: Option<Extensions>,
921}
922
923impl DeviceAuthorization {
924 /// Construct a new device authorization OAuth2 flow.
925 ///
926 /// First parameter is the device authorization endpoint URL where the client requests a
927 /// device code, second parameter is the token URL used to poll for the access token, and the
928 /// third parameter defines the scopes available for the flow.
929 ///
930 /// # Examples
931 ///
932 /// Create a device authorization flow and use it in an [`OAuth2`] security scheme.
933 /// ```rust
934 /// # use utoipa::openapi::security::{OAuth2, Flow, DeviceAuthorization, Scopes};
935 /// OAuth2::new([Flow::DeviceAuthorization(
936 /// DeviceAuthorization::new(
937 /// "https://localhost/oauth/device/code",
938 /// "https://localhost/oauth/token",
939 /// Scopes::from_iter([
940 /// ("edit:items", "edit my items"),
941 /// ("read:items", "read my items"),
942 /// ]),
943 /// ),
944 /// )]);
945 /// ```
946 pub fn new<D: Into<String>, T: Into<String>>(
947 device_authorization_url: D,
948 token_url: T,
949 scopes: Scopes,
950 ) -> Self {
951 Self {
952 device_authorization_url: device_authorization_url.into(),
953 token_url: token_url.into(),
954 refresh_url: None,
955 scopes,
956 extensions: Default::default(),
957 }
958 }
959
960 /// Construct a new device authorization OAuth2 flow with additional refresh URL.
961 ///
962 /// # Examples
963 ///
964 /// Create a device authorization flow with a refresh token URL.
965 /// ```rust
966 /// # use utoipa::openapi::security::{DeviceAuthorization, Scopes};
967 /// DeviceAuthorization::with_refresh_url(
968 /// "https://localhost/oauth/device/code",
969 /// "https://localhost/oauth/token",
970 /// Scopes::from_iter([("edit:items", "edit my items")]),
971 /// "https://localhost/oauth/refresh",
972 /// );
973 /// ```
974 pub fn with_refresh_url<S: Into<String>>(
975 device_authorization_url: S,
976 token_url: S,
977 scopes: Scopes,
978 refresh_url: S,
979 ) -> Self {
980 Self {
981 device_authorization_url: device_authorization_url.into(),
982 token_url: token_url.into(),
983 refresh_url: Some(refresh_url.into()),
984 scopes,
985 extensions: Default::default(),
986 }
987 }
988}
989
990/// Password [`Flow`] configuration for [`OAuth2`].
991#[non_exhaustive]
992#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
993#[serde(rename_all = "camelCase")]
994#[cfg_attr(feature = "debug", derive(Debug))]
995pub struct Password {
996 /// Token url for this OAuth2 flow. OAuth2 standard requires TLS.
997 pub token_url: String,
998
999 /// Optional refresh token url.
1000 #[serde(skip_serializing_if = "Option::is_none")]
1001 pub refresh_url: Option<String>,
1002
1003 /// Scopes required by the flow.
1004 #[serde(flatten)]
1005 pub scopes: Scopes,
1006
1007 /// Optional extensions "x-something".
1008 #[serde(skip_serializing_if = "Option::is_none", flatten)]
1009 pub extensions: Option<Extensions>,
1010}
1011
1012impl Password {
1013 /// Construct a new password oauth flow.
1014 ///
1015 /// Accepts two arguments: one which is a token url and
1016 /// two a map of scopes for oauth flow.
1017 ///
1018 /// # Examples
1019 ///
1020 /// Create new password flow with scopes.
1021 /// ```rust
1022 /// # use utoipa::openapi::security::{Password, Scopes};
1023 /// Password::new(
1024 /// "https://localhost/token",
1025 /// Scopes::from_iter([
1026 /// ("edit:items", "edit my items"),
1027 /// ("read:items", "read my items")
1028 /// ]),
1029 /// );
1030 /// ```
1031 ///
1032 /// Create new password flow without any scopes.
1033 /// ```rust
1034 /// # use utoipa::openapi::security::{Password, Scopes};
1035 /// Password::new(
1036 /// "https://localhost/token",
1037 /// Scopes::new(),
1038 /// );
1039 /// ```
1040 pub fn new<S: Into<String>>(token_url: S, scopes: Scopes) -> Self {
1041 Self {
1042 token_url: token_url.into(),
1043 refresh_url: None,
1044 scopes,
1045 extensions: Default::default(),
1046 }
1047 }
1048
1049 /// Construct a new password oauth flow with additional refresh url.
1050 ///
1051 /// This is essentially same as [`Password::new`] but allows defining third parameter for `refresh_url`
1052 /// for fetching refresh tokens.
1053 ///
1054 /// # Examples
1055 ///
1056 /// Create new password flow with refresh url.
1057 /// ```rust
1058 /// # use utoipa::openapi::security::{Password, Scopes};
1059 /// Password::with_refresh_url(
1060 /// "https://localhost/token",
1061 /// Scopes::from_iter([
1062 /// ("edit:items", "edit my items"),
1063 /// ("read:items", "read my items")
1064 /// ]),
1065 /// "https://localhost/refres-token"
1066 /// );
1067 /// ```
1068 pub fn with_refresh_url<S: Into<String>>(token_url: S, scopes: Scopes, refresh_url: S) -> Self {
1069 Self {
1070 token_url: token_url.into(),
1071 refresh_url: Some(refresh_url.into()),
1072 scopes,
1073 extensions: Default::default(),
1074 }
1075 }
1076}
1077
1078/// Client credentials [`Flow`] configuration for [`OAuth2`].
1079#[non_exhaustive]
1080#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
1081#[serde(rename_all = "camelCase")]
1082#[cfg_attr(feature = "debug", derive(Debug))]
1083pub struct ClientCredentials {
1084 /// Token url used for [`ClientCredentials`] flow. OAuth2 standard requires TLS.
1085 pub token_url: String,
1086
1087 /// Optional refresh token url.
1088 #[serde(skip_serializing_if = "Option::is_none")]
1089 pub refresh_url: Option<String>,
1090
1091 /// Scopes required by the flow.
1092 #[serde(flatten)]
1093 pub scopes: Scopes,
1094
1095 /// Optional extensions "x-something".
1096 #[serde(skip_serializing_if = "Option::is_none", flatten)]
1097 pub extensions: Option<Extensions>,
1098}
1099
1100impl ClientCredentials {
1101 /// Construct a new client credentials oauth flow.
1102 ///
1103 /// Accepts two arguments: one which is a token url and
1104 /// two a map of scopes for oauth flow.
1105 ///
1106 /// # Examples
1107 ///
1108 /// Create new client credentials flow with scopes.
1109 /// ```rust
1110 /// # use utoipa::openapi::security::{ClientCredentials, Scopes};
1111 /// ClientCredentials::new(
1112 /// "https://localhost/token",
1113 /// Scopes::from_iter([
1114 /// ("edit:items", "edit my items"),
1115 /// ("read:items", "read my items")
1116 /// ]),
1117 /// );
1118 /// ```
1119 ///
1120 /// Create new client credentials flow without any scopes.
1121 /// ```rust
1122 /// # use utoipa::openapi::security::{ClientCredentials, Scopes};
1123 /// ClientCredentials::new(
1124 /// "https://localhost/token",
1125 /// Scopes::new(),
1126 /// );
1127 /// ```
1128 pub fn new<S: Into<String>>(token_url: S, scopes: Scopes) -> Self {
1129 Self {
1130 token_url: token_url.into(),
1131 refresh_url: None,
1132 scopes,
1133 extensions: Default::default(),
1134 }
1135 }
1136
1137 /// Construct a new client credentials oauth flow with additional refresh url.
1138 ///
1139 /// This is essentially same as [`ClientCredentials::new`] but allows defining third parameter for
1140 /// `refresh_url`.
1141 ///
1142 /// # Examples
1143 ///
1144 /// Create new client credentials for with refresh url.
1145 /// ```rust
1146 /// # use utoipa::openapi::security::{ClientCredentials, Scopes};
1147 /// ClientCredentials::with_refresh_url(
1148 /// "https://localhost/token",
1149 /// Scopes::from_iter([
1150 /// ("edit:items", "edit my items"),
1151 /// ("read:items", "read my items")
1152 /// ]),
1153 /// "https://localhost/refresh-url"
1154 /// );
1155 /// ```
1156 pub fn with_refresh_url<S: Into<String>>(token_url: S, scopes: Scopes, refresh_url: S) -> Self {
1157 Self {
1158 token_url: token_url.into(),
1159 refresh_url: Some(refresh_url.into()),
1160 scopes,
1161 extensions: Default::default(),
1162 }
1163 }
1164}
1165
1166/// [`OAuth2`] flow scopes object defines required permissions for oauth flow.
1167///
1168/// Scopes must be given to oauth2 flow but depending on need one of few initialization methods
1169/// could be used.
1170///
1171/// * Create empty map of scopes you can use [`Scopes::new`].
1172/// * Create map with only one scope you can use [`Scopes::one`].
1173/// * Create multiple scopes from iterator with [`Scopes::from_iter`].
1174///
1175/// # Examples
1176///
1177/// Create empty map of scopes.
1178/// ```rust
1179/// # use utoipa::openapi::security::Scopes;
1180/// let scopes = Scopes::new();
1181/// ```
1182///
1183/// Create [`Scopes`] holding one scope.
1184/// ```rust
1185/// # use utoipa::openapi::security::Scopes;
1186/// let scopes = Scopes::one("edit:item", "edit pets");
1187/// ```
1188///
1189/// Create map of scopes from iterator.
1190/// ```rust
1191/// # use utoipa::openapi::security::Scopes;
1192/// let scopes = Scopes::from_iter([
1193/// ("edit:items", "edit my items"),
1194/// ("read:items", "read my items")
1195/// ]);
1196/// ```
1197#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
1198#[cfg_attr(feature = "debug", derive(Debug))]
1199pub struct Scopes {
1200 scopes: BTreeMap<String, String>,
1201}
1202
1203impl Scopes {
1204 /// Construct new [`Scopes`] with empty map of scopes. This is useful if oauth flow does not need
1205 /// any permission scopes.
1206 ///
1207 /// # Examples
1208 ///
1209 /// Create empty map of scopes.
1210 /// ```rust
1211 /// # use utoipa::openapi::security::Scopes;
1212 /// let scopes = Scopes::new();
1213 /// ```
1214 pub fn new() -> Self {
1215 Self {
1216 ..Default::default()
1217 }
1218 }
1219
1220 /// Construct new [`Scopes`] with holding one scope.
1221 ///
1222 /// * `scope` Is be the permission required.
1223 /// * `description` Short description about the permission.
1224 ///
1225 /// # Examples
1226 ///
1227 /// Create map of scopes with one scope item.
1228 /// ```rust
1229 /// # use utoipa::openapi::security::Scopes;
1230 /// let scopes = Scopes::one("edit:item", "edit items");
1231 /// ```
1232 pub fn one<S: Into<String>>(scope: S, description: S) -> Self {
1233 Self {
1234 scopes: BTreeMap::from_iter(iter::once_with(|| (scope.into(), description.into()))),
1235 }
1236 }
1237}
1238
1239impl<I> FromIterator<(I, I)> for Scopes
1240where
1241 I: Into<String>,
1242{
1243 fn from_iter<T: IntoIterator<Item = (I, I)>>(iter: T) -> Self {
1244 Self {
1245 scopes: iter
1246 .into_iter()
1247 .map(|(key, value)| (key.into(), value.into()))
1248 .collect(),
1249 }
1250 }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255 use super::*;
1256
1257 macro_rules! test_fn {
1258 ($name:ident: $schema:expr; $expected:literal) => {
1259 #[test]
1260 fn $name() {
1261 let value = serde_json::to_value($schema).unwrap();
1262 let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap();
1263
1264 assert_eq!(
1265 value,
1266 expected_value,
1267 "testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}",
1268 stringify!($name),
1269 value,
1270 expected_value
1271 );
1272
1273 println!("{}", &serde_json::to_string_pretty(&$schema).unwrap());
1274 }
1275 };
1276 }
1277
1278 test_fn! {
1279 security_scheme_correct_http_bearer_json:
1280 SecurityScheme::Http(
1281 HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build()
1282 );
1283 r###"{
1284 "type": "http",
1285 "scheme": "bearer",
1286 "bearerFormat": "JWT"
1287}"###
1288 }
1289
1290 test_fn! {
1291 security_scheme_correct_basic_auth:
1292 SecurityScheme::Http(Http::new(HttpAuthScheme::Basic));
1293 r###"{
1294 "type": "http",
1295 "scheme": "basic"
1296}"###
1297 }
1298
1299 test_fn! {
1300 security_scheme_correct_digest_auth:
1301 SecurityScheme::Http(Http::new(HttpAuthScheme::Digest));
1302 r###"{
1303 "type": "http",
1304 "scheme": "digest"
1305}"###
1306 }
1307
1308 test_fn! {
1309 security_scheme_correct_hoba_auth:
1310 SecurityScheme::Http(Http::new(HttpAuthScheme::Hoba));
1311 r###"{
1312 "type": "http",
1313 "scheme": "hoba"
1314}"###
1315 }
1316
1317 test_fn! {
1318 security_scheme_correct_mutual_auth:
1319 SecurityScheme::Http(Http::new(HttpAuthScheme::Mutual));
1320 r###"{
1321 "type": "http",
1322 "scheme": "mutual"
1323}"###
1324 }
1325
1326 test_fn! {
1327 security_scheme_correct_negotiate_auth:
1328 SecurityScheme::Http(Http::new(HttpAuthScheme::Negotiate));
1329 r###"{
1330 "type": "http",
1331 "scheme": "negotiate"
1332}"###
1333 }
1334
1335 test_fn! {
1336 security_scheme_correct_oauth_auth:
1337 SecurityScheme::Http(Http::new(HttpAuthScheme::OAuth));
1338 r###"{
1339 "type": "http",
1340 "scheme": "oauth"
1341}"###
1342 }
1343
1344 test_fn! {
1345 security_scheme_correct_scram_sha1_auth:
1346 SecurityScheme::Http(Http::new(HttpAuthScheme::ScramSha1));
1347 r###"{
1348 "type": "http",
1349 "scheme": "scram-sha-1"
1350}"###
1351 }
1352
1353 test_fn! {
1354 security_scheme_correct_scram_sha256_auth:
1355 SecurityScheme::Http(Http::new(HttpAuthScheme::ScramSha256));
1356 r###"{
1357 "type": "http",
1358 "scheme": "scram-sha-256"
1359}"###
1360 }
1361
1362 test_fn! {
1363 security_scheme_correct_api_key_cookie_auth:
1364 SecurityScheme::ApiKey(ApiKey::Cookie(ApiKeyValue::new(String::from("api_key"))));
1365 r###"{
1366 "type": "apiKey",
1367 "name": "api_key",
1368 "in": "cookie"
1369}"###
1370 }
1371
1372 test_fn! {
1373 security_scheme_correct_api_key_header_auth:
1374 SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("api_key")));
1375 r###"{
1376 "type": "apiKey",
1377 "name": "api_key",
1378 "in": "header"
1379}"###
1380 }
1381
1382 test_fn! {
1383 security_scheme_correct_api_key_query_auth:
1384 SecurityScheme::ApiKey(ApiKey::Query(ApiKeyValue::new(String::from("api_key"))));
1385 r###"{
1386 "type": "apiKey",
1387 "name": "api_key",
1388 "in": "query"
1389}"###
1390 }
1391
1392 test_fn! {
1393 security_scheme_correct_open_id_connect_auth:
1394 SecurityScheme::OpenIdConnect(OpenIdConnect::new("https://localhost/openid"));
1395 r###"{
1396 "type": "openIdConnect",
1397 "openIdConnectUrl": "https://localhost/openid"
1398}"###
1399 }
1400
1401 test_fn! {
1402 security_scheme_correct_oauth2_implicit:
1403 SecurityScheme::OAuth2(
1404 OAuth2::with_description([Flow::Implicit(
1405 Implicit::new(
1406 "https://localhost/auth/dialog",
1407 Scopes::from_iter([
1408 ("edit:items", "edit my items"),
1409 ("read:items", "read my items")
1410 ]),
1411 ),
1412 )], "my oauth2 flow")
1413 );
1414 r###"{
1415 "type": "oauth2",
1416 "flows": {
1417 "implicit": {
1418 "authorizationUrl": "https://localhost/auth/dialog",
1419 "scopes": {
1420 "edit:items": "edit my items",
1421 "read:items": "read my items"
1422 }
1423 }
1424 },
1425 "description": "my oauth2 flow"
1426}"###
1427 }
1428
1429 test_fn! {
1430 security_scheme_correct_oauth2_password:
1431 SecurityScheme::OAuth2(
1432 OAuth2::with_description([Flow::Password(
1433 Password::with_refresh_url(
1434 "https://localhost/oauth/token",
1435 Scopes::from_iter([
1436 ("edit:items", "edit my items"),
1437 ("read:items", "read my items")
1438 ]),
1439 "https://localhost/refresh/token"
1440 ),
1441 )], "my oauth2 flow")
1442 );
1443 r###"{
1444 "type": "oauth2",
1445 "flows": {
1446 "password": {
1447 "tokenUrl": "https://localhost/oauth/token",
1448 "refreshUrl": "https://localhost/refresh/token",
1449 "scopes": {
1450 "edit:items": "edit my items",
1451 "read:items": "read my items"
1452 }
1453 }
1454 },
1455 "description": "my oauth2 flow"
1456}"###
1457 }
1458
1459 test_fn! {
1460 security_scheme_correct_oauth2_client_credentials:
1461 SecurityScheme::OAuth2(
1462 OAuth2::new([Flow::ClientCredentials(
1463 ClientCredentials::with_refresh_url(
1464 "https://localhost/oauth/token",
1465 Scopes::from_iter([
1466 ("edit:items", "edit my items"),
1467 ("read:items", "read my items")
1468 ]),
1469 "https://localhost/refresh/token"
1470 ),
1471 )])
1472 );
1473 r###"{
1474 "type": "oauth2",
1475 "flows": {
1476 "clientCredentials": {
1477 "tokenUrl": "https://localhost/oauth/token",
1478 "refreshUrl": "https://localhost/refresh/token",
1479 "scopes": {
1480 "edit:items": "edit my items",
1481 "read:items": "read my items"
1482 }
1483 }
1484 }
1485}"###
1486 }
1487
1488 test_fn! {
1489 security_scheme_correct_oauth2_authorization_code:
1490 SecurityScheme::OAuth2(
1491 OAuth2::new([Flow::AuthorizationCode(
1492 AuthorizationCode::with_refresh_url(
1493 "https://localhost/authorization/token",
1494 "https://localhost/token/url",
1495 Scopes::from_iter([
1496 ("edit:items", "edit my items"),
1497 ("read:items", "read my items")
1498 ]),
1499 "https://localhost/refresh/token"
1500 ),
1501 )])
1502 );
1503 r###"{
1504 "type": "oauth2",
1505 "flows": {
1506 "authorizationCode": {
1507 "authorizationUrl": "https://localhost/authorization/token",
1508 "tokenUrl": "https://localhost/token/url",
1509 "refreshUrl": "https://localhost/refresh/token",
1510 "scopes": {
1511 "edit:items": "edit my items",
1512 "read:items": "read my items"
1513 }
1514 }
1515 }
1516}"###
1517 }
1518
1519 test_fn! {
1520 security_scheme_correct_oauth2_authorization_code_no_scopes:
1521 SecurityScheme::OAuth2(
1522 OAuth2::new([Flow::AuthorizationCode(
1523 AuthorizationCode::with_refresh_url(
1524 "https://localhost/authorization/token",
1525 "https://localhost/token/url",
1526 Scopes::new(),
1527 "https://localhost/refresh/token"
1528 ),
1529 )])
1530 );
1531 r###"{
1532 "type": "oauth2",
1533 "flows": {
1534 "authorizationCode": {
1535 "authorizationUrl": "https://localhost/authorization/token",
1536 "tokenUrl": "https://localhost/token/url",
1537 "refreshUrl": "https://localhost/refresh/token",
1538 "scopes": {}
1539 }
1540 }
1541}"###
1542 }
1543
1544 test_fn! {
1545 security_scheme_correct_mutual_tls:
1546 SecurityScheme::MutualTls {
1547 description: Some(String::from("authorization is performed with client side certificate")),
1548 deprecated: None,
1549 extensions: None,
1550 };
1551 r###"{
1552 "type": "mutualTLS",
1553 "description": "authorization is performed with client side certificate"
1554}"###
1555 }
1556}