oas3 0.21.0

Structures and tools to parse, navigate, and validate OpenAPI v3.1.xß specifications
Documentation
use serde::{Deserialize, Serialize};

use super::Flows;
use crate::{
    spec::{FromRef, Ref, RefError, RefType},
    Spec,
};

/// Defines a security scheme that can be used by the operations.
///
/// Supported schemes are HTTP authentication, an API key (either as a header or as a query
/// parameter), OAuth2's common flows (implicit, password, application and access code) as defined
/// in [RFC6749], and [OpenID Connect Discovery].
///
/// See <https://spec.openapis.org/oas/v3.1.1#security-scheme-object>.
///
/// [RFC6749]: https://tools.ietf.org/html/rfc6749
/// [OpenID Connect Discovery]: https://tools.ietf.org/html/draft-ietf-oauth-discovery-06
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum SecurityScheme {
    /// API key authentication.
    #[serde(rename = "apiKey")]
    ApiKey {
        /// Description for security scheme.
        ///
        /// [CommonMark syntax](https://spec.commonmark.org) MAY be used for rich text
        /// representation.
        #[serde(skip_serializing_if = "Option::is_none")]
        description: Option<String>,

        /// The name of the header, query or cookie parameter to be used.
        name: String,

        /// The location of the API key. Valid values are `"query"`, `"header"`, or `"cookie"`.
        #[serde(rename = "in")]
        location: String,
    },

    /// HTTP authentication.
    #[serde(rename = "http")]
    Http {
        /// Description for security scheme.
        ///
        /// [CommonMark syntax](https://spec.commonmark.org) MAY be used for rich text
        /// representation.
        #[serde(skip_serializing_if = "Option::is_none")]
        description: Option<String>,

        /// Name of the HTTP Authorization scheme to be used in the Authorization header as defined
        /// in RFC 7235.
        ///
        /// The values used SHOULD be registered in the IANA Authentication Scheme registry.
        scheme: String,

        /// A hint to the client to identify how the bearer token is formatted.
        ///
        /// Bearer tokens are usually generated by an authorization server, so this information is
        /// primarily for documentation purposes.
        #[serde(rename = "bearerFormat")]
        bearer_format: Option<String>,
    },

    /// OAuth2 authentication.
    #[serde(rename = "oauth2")]
    OAuth2 {
        /// Description for security scheme.
        ///
        /// [CommonMark syntax](https://spec.commonmark.org) MAY be used for rich text
        /// representation.
        #[serde(skip_serializing_if = "Option::is_none")]
        description: Option<String>,

        /// An object containing configuration information for the flow types supported.
        flows: Flows,
    },

    /// OpenID Connect authentication.
    #[serde(rename = "openIdConnect")]
    OpenIdConnect {
        /// Description for security scheme.
        ///
        /// [CommonMark syntax](https://spec.commonmark.org) MAY be used for rich text
        /// representation.
        #[serde(skip_serializing_if = "Option::is_none")]
        description: Option<String>,

        /// OpenID Connect URL to discover OAuth2 configuration values.
        ///
        /// The OpenID Connect standard requires the use of TLS.
        #[serde(rename = "openIdConnectUrl")]
        open_id_connect_url: String,
    },

    /// Mutual TLS authentication.
    #[serde(rename = "mutualTLS")]
    MutualTls {
        /// Description for security scheme.
        ///
        /// [CommonMark syntax](https://spec.commonmark.org) MAY be used for rich text
        /// representation.
        #[serde(skip_serializing_if = "Option::is_none")]
        description: Option<String>,
    },
}

impl FromRef for SecurityScheme {
    fn from_ref(spec: &Spec, path: &str) -> Result<Self, RefError> {
        let refpath = path.parse::<Ref>()?;

        match refpath.kind {
            RefType::SecurityScheme => spec
                .components
                .as_ref()
                .and_then(|cs| cs.security_schemes.get(&refpath.name))
                .ok_or_else(|| RefError::Unresolvable(path.to_owned()))
                .and_then(|oor| oor.resolve(spec)),
            typ => Err(RefError::MismatchedType(typ, RefType::SecurityScheme)),
        }
    }
}

#[cfg(test)]
mod tests {
    use url::Url;

    use super::*;

    #[test]
    fn test_http_basic_deser() {
        const HTTP_BASIC_SAMPLE: &str = r#"{"type": "http", "scheme": "basic"}"#;
        let obj: SecurityScheme = serde_json::from_str(HTTP_BASIC_SAMPLE).unwrap();

        assert!(matches!(
            obj,
            SecurityScheme::Http {
                description: None,
                scheme,
                bearer_format: None,
            } if scheme == "basic"
        ));
    }

    #[test]
    fn test_security_scheme_oauth_deser() {
        const IMPLICIT_OAUTH2_SAMPLE: &str = r#"{
          "type": "oauth2",
          "flows": {
            "implicit": {
              "authorizationUrl": "https://example.com/api/oauth/dialog",
              "scopes": {
                "write:pets": "modify pets in your account",
                "read:pets": "read your pets"
              }
            },
            "authorizationCode": {
              "authorizationUrl": "https://example.com/api/oauth/dialog",
              "tokenUrl": "https://example.com/api/oauth/token",
              "scopes": {
                "write:pets": "modify pets in your account",
                "read:pets": "read your pets"
              }
            }
          }
        }"#;

        let obj: SecurityScheme = serde_json::from_str(IMPLICIT_OAUTH2_SAMPLE).unwrap();
        match obj {
            SecurityScheme::OAuth2 {
                description: _,
                flows,
            } => {
                assert!(flows.implicit.is_some());
                let implicit = flows.implicit.unwrap();
                assert_eq!(
                    implicit.authorization_url,
                    Url::parse("https://example.com/api/oauth/dialog").unwrap()
                );
                assert!(implicit.scopes.contains_key("write:pets"));
                assert!(implicit.scopes.contains_key("read:pets"));

                assert!(flows.authorization_code.is_some());
                let auth_code = flows.authorization_code.unwrap();
                assert_eq!(
                    auth_code.authorization_url,
                    Url::parse("https://example.com/api/oauth/dialog").unwrap()
                );
                assert_eq!(
                    auth_code.token_url,
                    Url::parse("https://example.com/api/oauth/token").unwrap()
                );
                assert!(implicit.scopes.contains_key("write:pets"));
                assert!(implicit.scopes.contains_key("read:pets"));
            }
            _ => panic!("wrong security scheme type"),
        }
    }
}