#![deny(missing_docs)]

//! # Http Signature Normalization Actix Extractor
//! _Experimental Extractor for request signatures_
//!
//! This library takes a different approach from the other implementation, which prefers
//! Middlewares.
//!
//! ```rust
//! use actix_web::{http::StatusCode, web, App, HttpRequest, HttpResponse, HttpServer, ResponseError};
//! use http_signature_normalization_actix_extractor::{
//!     Algorithm, Config, ConfigGenerator, DeprecatedAlgorithm, Signed, VerifyKey,
//! };
//! use sha2::Sha256;
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//!     /*
//!     HttpServer::new(|| App::new().route("/", web::post().to(protected)))
//!         .bind("127.0.0.1:8010")?
//!         .run()
//!         .await
//!     */
//!     Ok(())
//! }
//!
//! async fn protected(signed_request: Signed<String, Cfg, Sha256, Key>) -> &'static str {
//!     let (value, signature) = signed_request.into_parts();
//!
//!     println!("{}", value);
//!     println!("{:#?}", signature);
//!
//!     "hewwo, mr obama"
//! }
//!
//! pub struct Cfg;
//!
//! #[derive(Debug)]
//! pub struct Key;
//!
//! #[derive(Debug)]
//! pub enum VerifyError {
//!     Algorithm,
//!     Decode,
//!     Key,
//! }
//!
//! impl std::fmt::Display for VerifyError {
//!     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
//!         match self {
//!             Self::Algorithm => f.write_str("Unsupported algorithm"),
//!             Self::Decode => f.write_str("Couldn't decode signature"),
//!             Self::Key => f.write_str("Invalid key"),
//!         }
//!     }
//! }
//!
//! impl std::error::Error for VerifyError {}
//!
//! impl ConfigGenerator for Cfg {
//!     fn config() -> Config {
//!         Config::new().require_header("accept")
//!     }
//! }
//!
//! impl VerifyKey for Key {
//!     type Error = VerifyError;
//!
//!     async fn init(
//!         _: &HttpRequest,
//!         key_id: &str,
//!         algorithm: Option<&Algorithm>,
//!     ) -> Result<Self, Self::Error> {
//!         match algorithm {
//!             Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
//!             _ => return Err(VerifyError::Algorithm),
//!         };
//!
//!         if key_id != "my-key-id" {
//!             return Err(VerifyError::Key);
//!         }
//!
//!         Ok(Key)
//!     }
//!
//!     fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error> {
//!         use subtle::ConstantTimeEq;
//!
//!         let decoded = base64::decode(&signature).map_err(|_| VerifyError::Decode)?;
//!
//!         Ok(decoded.ct_eq(signing_string.as_bytes()).into())
//!     }
//! }
//!
//! impl ResponseError for VerifyError {
//!     fn status_code(&self) -> StatusCode {
//!         StatusCode::BAD_REQUEST
//!     }
//!
//!     fn error_response(&self) -> HttpResponse {
//!         HttpResponse::BadRequest().finish()
//!     }
//! }
//! ```

pub use actix_web_lab::extract::RequestSignature;
pub use http_signature_normalization::{
    verify::{Algorithm, DeprecatedAlgorithm},
    Config, PrepareVerifyError,
};

/// An alias to simplify extracting a signed request
/// ```rust,ignore
/// async fn protected(_: Signed<String, (), (), Key>) -> &'static str {
///     "hewwo, mr obama"
/// }
/// ```
pub type Signed<Extractor, Config, Digest, VerifyKey> =
    RequestSignature<Extractor, SignedRequest<Config, Digest, VerifyKey>>;

#[cfg(feature = "sha-2")]
mod sha2_digest;

#[derive(Debug)]
/// Errors produced by the Extractor
pub enum Error {
    /// The provided Digest header was invalid
    Digest,

    /// The provided Signature or Authorization header was invalid
    Signature,

    /// Another header required for verifying the signature is invalid
    InvalidHeaderValue,

    /// There was an error preparing for verification
    PrepareVerify(PrepareVerifyError),
}

/// The SignedRequest Signature scheme
///
/// This implements SignatureScheme to allow extracing a signed request
pub struct SignedRequest<C, D, K> {
    config_generator: std::marker::PhantomData<C>,
    key: std::marker::PhantomData<K>,
    digest_verifier: Option<D>,
    unverified: http_signature_normalization::verify::Unverified,
}

#[derive(Debug)]
/// A parsed part of a Digest header
pub struct DigestPart {
    /// The Algorithm this digest was computed with
    pub algorithm: String,

    /// The base64-encoded digest
    pub digest: String,
}

#[derive(Debug)]
/// All the required pieces for validating a request signature
pub struct Signature<D, K> {
    key: K,
    unverified: http_signature_normalization::verify::Unverified,
    digest_verifier: D,
    digest_parts: Option<Vec<DigestPart>>,
}

/// Injects a customized [`Config`] type for signature verification
///
/// If you don't need to customize the `Config`, you can use `()`
///
/// ```rust
/// use http_signature_normalization_actix_extractor::{Config, ConfigGenerator};
///
/// struct Gen;
///
/// impl ConfigGenerator for Gen {
///     fn config() -> Config {
///         Config::new()
///     }
/// }
/// ```
pub trait ConfigGenerator {
    /// Produce a new `Config`
    fn config() -> Config;
}

/// Extracts a key from the request
///
/// ```rust
/// use actix_web::{http::StatusCode, web::Data, HttpRequest, HttpResponse, ResponseError};
/// use http_signature_normalization_actix_extractor::{Algorithm, DeprecatedAlgorithm, VerifyKey};
/// use openssl::{hash::MessageDigest, pkey::{PKey, Public}, sign::Verifier};
///
/// pub struct OpenSSLPublicKey(PKey<Public>);
///
/// impl VerifyKey for OpenSSLPublicKey {
///     type Error = VerifyError;
///
///     async fn init(
///         req: &HttpRequest,
///         key_id: &str,
///         algorithm: Option<&Algorithm>,
///     ) -> Result<Self, Self::Error> {
///         match algorithm {
///             Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
///             _ => return Err(VerifyError::Algorithm),
///         };
///
///         if key_id != "my-key-id" {
///             return Err(VerifyError::Key);
///         }
///
///         let key = req.app_data::<Data<PKey<Public>>>().expect("Key loaded").as_ref().clone();
///
///         Ok(OpenSSLPublicKey(key))
///     }
///
///     fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error> {
///         let decoded = openssl::base64::decode_block(&signature).map_err(|_| VerifyError::Decode)?;
///
///         let verifier = Verifier::new(MessageDigest::sha256(), &self.0).map_err(|_| VerifyError::Verifier)?;
///
///         verifier.verify(&decoded).map_err(|_| VerifyError::Verify)
///     }
/// }
///
/// #[derive(Debug)]
/// pub enum VerifyError {
///     Algorithm,
///     Decode,
///     Key,
///     Verifier,
///     Verify,
/// }
///
/// impl std::fmt::Display for VerifyError {
///     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
///         match self {
///             Self::Algorithm => f.write_str("Unsupported algorithm"),
///             Self::Decode => f.write_str("Couldn't decode signature"),
///             Self::Key => f.write_str("Invalid key"),
///             Self::Verifier => f.write_str("Failed to create Verifier from key"),
///             Self::Verify => f.write_str("Failed to verify signature"),
///         }
///     }
/// }
///
/// impl std::error::Error for VerifyError {}
///
/// impl ResponseError for VerifyError {
///     fn status_code(&self) -> StatusCode {
///         StatusCode::BAD_REQUEST
///     }
///
///     fn error_response(&self) -> HttpResponse {
///         HttpResponse::BadRequest().finish()
///     }
/// }
/// ```
pub trait VerifyKey: Sized + Send {
    /// Errors that can happen when extracting keys or verifying the signature
    type Error: Into<actix_web::Error>;

    /// Extract the key from the request, given the key_id and algorithm
    fn init(
        req: &actix_web::HttpRequest,
        key_id: &str,
        algorithm: Option<&Algorithm>,
    ) -> impl std::future::Future<Output = Result<Self, Self::Error>>;

    /// Verify the signature with the given signing string
    fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error>;
}

/// Verifies the Digest header from the request
///
/// For endpoints that do not accept request bodies, `()` can be used as the verifier
///
/// ### Example:
/// ```rust
/// use http_signature_normalization_actix_extractor::{DigestPart, VerifyDigest};
/// use openssl::sha::Sha256;
///
/// struct OpenSSLSha256(Option<Sha256>);
///
/// impl Default for OpenSSLSha256 {
///     fn default() -> Self {
///         Self::new()
///     }
/// }
///
/// impl OpenSSLSha256 {
///     fn new() -> Self {
///         Self(Some(Sha256::new()))
///     }
/// }
///
/// impl VerifyDigest for OpenSSLSha256 {
///     fn update(&mut self, bytes: &[u8]) {
///         self.0.as_mut().expect("Update called after verify").update(bytes);
///     }
///
///     fn verify(&mut self, parts: &[DigestPart]) -> bool {
///         if let Some(decoded) = parts.iter().find_map(|p| {
///             if p.algorithm.to_lowercase() == "sha-256" {
///                 openssl::base64::decode_block(&p.digest).ok()
///             } else {
///                 None
///             }
///         }) {
///             return openssl::memcmp::eq(
///                 &self.0.take().expect("verify called more than once").finish(),
///                 &decoded,
///             );
///         }
///
///         false
///     }
/// }
/// ```
pub trait VerifyDigest: Default + Send + 'static {
    /// Whether to run this verifier
    const REQUIRED: bool = true;

    /// Update the verifier with th eprovided bytes
    fn update(&mut self, bytes: &[u8]);

    /// Given a slice of parts, verify that the one matching the current verifier is valid
    fn verify(&mut self, parts: &[DigestPart]) -> bool;
}

impl<C, D, K> actix_web_lab::extract::RequestSignatureScheme for SignedRequest<C, D, K>
where
    C: ConfigGenerator,
    D: VerifyDigest,
    K: VerifyKey,
{
    type Signature = Signature<D, K>;

    type Error = actix_web::Error;

    async fn init(req: &actix_web::HttpRequest) -> Result<Self, Self::Error> {
        let config = C::config();

        let unverified = begin_verify(&config, req.method(), req.uri(), req.headers())?;

        Ok(SignedRequest {
            config_generator: std::marker::PhantomData,
            key: std::marker::PhantomData,
            digest_verifier: Some(D::default()),
            unverified,
        })
    }

    async fn consume_chunk(
        &mut self,
        _: &actix_web::HttpRequest,
        chunk: actix_web::web::Bytes,
    ) -> Result<(), Self::Error> {
        if D::REQUIRED {
            let mut verifier = self
                .digest_verifier
                .take()
                .expect("consume_chunk polled concurrently");

            self.digest_verifier = Some(
                actix_web::web::block(move || {
                    verifier.update(&chunk);
                    verifier
                })
                .await
                .expect("Panic in verifier update"),
            );
        }

        Ok(())
    }

    async fn finalize(
        mut self,
        req: &actix_web::HttpRequest,
    ) -> Result<Self::Signature, Self::Error> {
        let key = K::init(req, self.unverified.key_id(), self.unverified.algorithm())
            .await
            .map_err(Into::into)?;

        let digest_parts = if D::REQUIRED {
            req.headers().get("digest").and_then(parse_digest)
        } else {
            None
        };

        Ok(Signature {
            key,
            unverified: self.unverified,
            digest_verifier: self
                .digest_verifier
                .take()
                .expect("finalize called more than once"),
            digest_parts,
        })
    }

    fn verify(
        signature: Self::Signature,
        _: &actix_web::HttpRequest,
    ) -> Result<Self::Signature, Self::Error> {
        let Signature {
            mut key,
            unverified,
            mut digest_verifier,
            digest_parts,
        } = signature;

        if !unverified
            .verify(|sig, signing_string| key.verify(sig, signing_string))
            .map_err(Into::into)?
        {
            return Err(Error::Signature.into());
        }

        if D::REQUIRED && !digest_verifier.verify(digest_parts.as_deref().unwrap_or(&[])) {
            return Err(Error::Digest.into());
        }

        let signature = Signature {
            key,
            unverified,
            digest_verifier,
            digest_parts,
        };

        Ok(signature)
    }
}

fn parse_digest(h: &actix_web::http::header::HeaderValue) -> Option<Vec<DigestPart>> {
    let h = h.to_str().ok()?.split(';').next()?;
    let v: Vec<_> = h
        .split(',')
        .filter_map(|p| {
            let mut iter = p.splitn(2, '=');
            iter.next()
                .and_then(|alg| iter.next().map(|value| (alg, value)))
        })
        .map(|(alg, value)| DigestPart {
            algorithm: alg.to_owned(),
            digest: value.to_owned(),
        })
        .collect();

    if v.is_empty() {
        None
    } else {
        Some(v)
    }
}

fn begin_verify(
    config: &Config,
    method: &actix_web::http::Method,
    uri: &actix_web::http::Uri,
    headers: &actix_web::http::header::HeaderMap,
) -> Result<http_signature_normalization::verify::Unverified, Error> {
    let mut headers = headers
        .iter()
        .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
        .collect::<Result<std::collections::BTreeMap<_, _>, actix_web::http::header::ToStrError>>(
        )?;

    if !headers.contains_key("host") {
        if let Some(host) = uri.host() {
            let host = if let Some(port) = uri.port() {
                format!("{host}:{port}")
            } else {
                host.to_string()
            };
            headers.insert("host".to_string(), host);
        }
    }

    let path_and_query = uri
        .path_and_query()
        .map(|p| p.to_string())
        .unwrap_or_else(|| "/".to_string());

    let unverified = config.begin_verify(method.as_ref(), &path_and_query, headers)?;

    Ok(unverified)
}

impl ConfigGenerator for () {
    fn config() -> Config {
        Config::new()
    }
}

impl VerifyDigest for () {
    const REQUIRED: bool = false;
    fn update(&mut self, _: &[u8]) {}
    fn verify(&mut self, _: &[DigestPart]) -> bool {
        true
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Digest => write!(f, "Digest is inavlid"),
            Self::Signature => write!(f, "Signature is invalid"),
            Self::InvalidHeaderValue => write!(f, "Invalid header value"),
            Self::PrepareVerify(_) => write!(f, "Error preparint verification"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::PrepareVerify(ref e) => Some(e),
            Self::Digest => None,
            Self::Signature => None,
            Self::InvalidHeaderValue => None,
        }
    }
}

impl actix_web::ResponseError for Error {
    fn status_code(&self) -> actix_web::http::StatusCode {
        actix_web::http::StatusCode::BAD_REQUEST
    }

    fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
        actix_web::HttpResponse::build(self.status_code()).finish()
    }
}

impl From<PrepareVerifyError> for Error {
    fn from(e: PrepareVerifyError) -> Self {
        Error::PrepareVerify(e)
    }
}

impl From<actix_web::http::header::ToStrError> for Error {
    fn from(_: actix_web::http::header::ToStrError) -> Self {
        Error::InvalidHeaderValue
    }
}
