From ad041c8f610e2b3b71c43b3e3a743cc716f07f76 Mon Sep 17 00:00:00 2001 From: Stefan Hausotte Date: Fri, 3 Nov 2023 12:32:36 +0100 Subject: [PATCH] Refactor Rust SDK for multiple autentication methods . . Rust: move auth to own module . . . . . Rust: restructure again refactoring of auth module finished add docs and update deps --- rust/Cargo.toml | 12 +- rust/src/auth/authenticator.rs | 12 ++ .../auth/authenticators/client_credentials.rs | 105 +++++++++++++++++ rust/src/auth/authenticators/mod.rs | 9 ++ rust/src/auth/authenticators/password.rs | 109 ++++++++++++++++++ rust/src/auth/mod.rs | 8 ++ rust/src/authenticator.rs | 7 -- rust/src/builder.rs | 45 ++++---- rust/src/c | 0 rust/src/connection.rs | 13 ++- rust/src/error.rs | 2 +- rust/src/lib.rs | 41 ++----- rust/src/message/message_type.rs | 13 ++- rust/src/message/verdict.rs | 2 +- rust/src/options.rs | 1 - ...urce_owner_password_grant_authenticator.rs | 77 ------------- rust/src/vaas.rs | 88 ++------------ rust/tests/real_api_integration_tests.rs | 39 +------ 18 files changed, 313 insertions(+), 270 deletions(-) create mode 100644 rust/src/auth/authenticator.rs create mode 100644 rust/src/auth/authenticators/client_credentials.rs create mode 100644 rust/src/auth/authenticators/mod.rs create mode 100644 rust/src/auth/authenticators/password.rs create mode 100644 rust/src/auth/mod.rs delete mode 100644 rust/src/authenticator.rs create mode 100644 rust/src/c delete mode 100644 rust/src/resource_owner_password_grant_authenticator.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8fc86ab0..53ddc4b3 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,15 +15,15 @@ websockets = "0.3.0" serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" thiserror = "1.0" -uuid = { version = "1.4.1", features = ["serde", "v4"] } +uuid = { version = "1.5", features = ["serde", "v4"] } reqwest = "0.11" -regex = "1.9" -tokio = { version = "1.32", features = ["sync", "fs"] } -sha2 = "0.10.8" +regex = "1.10" +tokio = { version = "1.33", features = ["sync", "fs"] } +sha2 = "0.10" futures = "0.3" rand = "0.8" -async-trait = "0.1.73" +async-trait = "0.1" [dev-dependencies] dotenv = "0.15" -tokio = { version = "1.32", features = ["rt", "macros", "rt-multi-thread"] } \ No newline at end of file +tokio = { version = "1.33", features = ["rt", "macros", "rt-multi-thread"] } diff --git a/rust/src/auth/authenticator.rs b/rust/src/auth/authenticator.rs new file mode 100644 index 00000000..30141b9f --- /dev/null +++ b/rust/src/auth/authenticator.rs @@ -0,0 +1,12 @@ +use crate::error::VResult; +use async_trait::async_trait; + +pub static DEFAULT_TOKEN_URL: &str = + "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"; + +/// This trait has to be implemented by any authentication methods for VaaS. +#[async_trait] +pub trait Authenticator { + /// Return a valid token that can be used to authenticate against the VaaS service. + async fn get_token(&self) -> VResult; +} diff --git a/rust/src/auth/authenticators/client_credentials.rs b/rust/src/auth/authenticators/client_credentials.rs new file mode 100644 index 00000000..068b28fb --- /dev/null +++ b/rust/src/auth/authenticators/client_credentials.rs @@ -0,0 +1,105 @@ +use crate::auth::authenticator::{Authenticator, DEFAULT_TOKEN_URL}; +use crate::error::{Error, VResult}; +use crate::message::OpenIdConnectTokenResponse; +use async_trait::async_trait; +use reqwest::StatusCode; +use reqwest::Url; + +/// Authenticator for the VaaS service using the client credentials flow. +/// Expects a client id and a client secret. +pub struct ClientCredentials { + client_id: String, + client_secret: String, + token_url: Url, +} + +impl ClientCredentials { + /// Create a new authenticator for the VaaS service using the client credentials flow. + pub fn new(client_id: String, client_secret: String) -> Self { + Self { + client_id, + client_secret, + token_url: Url::parse(DEFAULT_TOKEN_URL).unwrap(), // Safe to unwrap, as this is a constant URL and will always be valid. + } + } + /// Set the token URL to be used for authentication. + pub fn with_token_url(mut self, token_url: Url) -> Self { + self.token_url = token_url; + self + } +} + +#[async_trait] +impl Authenticator for ClientCredentials { + async fn get_token(&self) -> VResult { + let params = [ + ("client_id", self.client_id.clone()), + ("client_secret", self.client_secret.clone()), + ("grant_type", "client_credentials".to_string()), + ]; + let client = reqwest::Client::new(); + let token_response = client + .post(self.token_url.clone()) + .form(¶ms) + .send() + .await?; + + match token_response.status() { + StatusCode::OK => { + let json_string = token_response.text().await?; + Ok(OpenIdConnectTokenResponse::try_from(&json_string)?.access_token) + } + status => Err(Error::FailedAuthTokenRequest( + status, + token_response.text().await.unwrap_or_default(), + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::error::Error::FailedAuthTokenRequest; + + #[tokio::test] + async fn authenticator_returns_token() { + let token_url: Url = dotenv::var("TOKEN_URL") + .expect("No TOKEN_URL environment variable set to be used in the integration tests") + .parse() + .expect("Failed to parse TOKEN_URL environment variable"); + let client_id = dotenv::var("CLIENT_ID").expect( + "No VAAS_CLIENT_ID environment variable set to be used in the integration tests", + ); + let client_secret = dotenv::var("CLIENT_SECRET").expect( + "No VAAS_PASSWORD environment variable set to be used in the integration tests", + ); + let authenticator = + ClientCredentials::new(client_id, client_secret).with_token_url(token_url); + + let token = authenticator.get_token().await; + + assert!(token.is_ok()) + } + + #[tokio::test] + async fn authenticator_wrong_credentials() { + let token_url: Url = dotenv::var("TOKEN_URL") + .expect("No TOKEN_URL environment variable set to be used in the integration tests") + .parse() + .expect("Failed to parse TOKEN_URL environment variable"); + let client_id = "invalid".to_string(); + let client_secret = "invalid".to_string(); + let authenticator = + ClientCredentials::new(client_id, client_secret).with_token_url(token_url); + + let token = authenticator.get_token().await; + + assert!(token.is_err()); + assert!(match token { + Ok(_) => false, + Err(FailedAuthTokenRequest(_, _)) => true, + _ => false, + }) + } +} diff --git a/rust/src/auth/authenticators/mod.rs b/rust/src/auth/authenticators/mod.rs new file mode 100644 index 00000000..ade49b67 --- /dev/null +++ b/rust/src/auth/authenticators/mod.rs @@ -0,0 +1,9 @@ +//! # Authenticators +//! +//! This module contains the different **OAuth2 Grant Types** that can be used to authenticate against the VaaS service. + +mod client_credentials; +mod password; + +pub use client_credentials::ClientCredentials; +pub use password::Password; diff --git a/rust/src/auth/authenticators/password.rs b/rust/src/auth/authenticators/password.rs new file mode 100644 index 00000000..4ae3cc17 --- /dev/null +++ b/rust/src/auth/authenticators/password.rs @@ -0,0 +1,109 @@ +use crate::auth::Authenticator; +use crate::error::{Error, VResult}; +use crate::message::OpenIdConnectTokenResponse; +use async_trait::async_trait; +use reqwest::{StatusCode, Url}; + +/// Authenticator for the VaaS service using the password flow. +/// Expects a client id, a user name and a password. +pub struct Password { + client_id: String, + user_name: String, + password: String, + token_url: Url, +} + +impl Password { + /// Create a new authenticator for the VaaS service using the password flow. + pub fn new(client_id: String, user_name: String, password: String) -> Self { + Self { + client_id, + user_name, + password, + token_url: Url::parse(crate::auth::authenticator::DEFAULT_TOKEN_URL).unwrap(), // Safe to unwrap, as this is a constant URL and will always be valid. + } + } + /// Set the token URL to be used for authentication. + pub fn with_token_url(mut self, token_url: Url) -> Self { + self.token_url = token_url; + self + } +} + +#[async_trait] +impl Authenticator for Password { + async fn get_token(&self) -> VResult { + let params = [ + ("client_id", self.client_id.clone()), + ("username", self.user_name.clone()), + ("password", self.password.clone()), + ("grant_type", "password".to_string()), + ]; + let client = reqwest::Client::new(); + let token_response = client + .post(self.token_url.clone()) + .form(¶ms) + .send() + .await?; + + match token_response.status() { + StatusCode::OK => { + let json_string = token_response.text().await?; + Ok(OpenIdConnectTokenResponse::try_from(&json_string)?.access_token) + } + status => Err(Error::FailedAuthTokenRequest( + status, + token_response.text().await.unwrap_or_default(), + )), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::error::Error::FailedAuthTokenRequest; + + #[tokio::test] + async fn authenticator_returns_token() { + let token_url: Url = dotenv::var("TOKEN_URL") + .expect("No TOKEN_URL environment variable set to be used in the integration tests") + .parse() + .expect("Failed to parse TOKEN_URL environment variable"); + let client_id = dotenv::var("VAAS_CLIENT_ID").expect( + "No VAAS_CLIENT_ID environment variable set to be used in the integration tests", + ); + let user_name = dotenv::var("VAAS_USER_NAME").expect( + "No VAAS_USER_NAME environment variable set to be used in the integration tests", + ); + let password = dotenv::var("VAAS_PASSWORD").expect( + "No VAAS_PASSWORD environment variable set to be used in the integration tests", + ); + let authenticator = Password::new(client_id, user_name, password).with_token_url(token_url); + + let token = authenticator.get_token().await; + + assert!(token.is_ok()) + } + + #[tokio::test] + async fn authenticator_wrong_credentials() { + let token_url: Url = dotenv::var("TOKEN_URL") + .expect("No TOKEN_URL environment variable set to be used in the integration tests") + .parse() + .expect("Failed to parse TOKEN_URL environment variable"); + let client_id = "invalid".to_string(); + let user_name = "invalid".to_string(); + let password = "invalid".to_string(); + let authenticator = Password::new(client_id, user_name, password).with_token_url(token_url); + + let token = authenticator.get_token().await; + + assert!(token.is_err()); + assert!(match token { + Ok(_) => false, + Err(FailedAuthTokenRequest(_, _)) => true, + _ => false, + }) + } +} diff --git a/rust/src/auth/mod.rs b/rust/src/auth/mod.rs new file mode 100644 index 00000000..1cd99721 --- /dev/null +++ b/rust/src/auth/mod.rs @@ -0,0 +1,8 @@ +//! # Authentication +//! +//! This module contains all needed funcionality to authenticate against the VaaS service. + +mod authenticator; +pub mod authenticators; + +pub use authenticator::Authenticator; diff --git a/rust/src/authenticator.rs b/rust/src/authenticator.rs deleted file mode 100644 index d4fd5569..00000000 --- a/rust/src/authenticator.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::error::VResult; -use async_trait::async_trait; - -#[async_trait] -pub trait Authenticator { - async fn get_token(self) -> VResult; -} diff --git a/rust/src/builder.rs b/rust/src/builder.rs index 690acc97..b6025675 100644 --- a/rust/src/builder.rs +++ b/rust/src/builder.rs @@ -1,31 +1,41 @@ //! The `Builder` struct create a new [Vaas] instance with the expected default values and allows the custom configuration. -use reqwest::Url; +use crate::auth::Authenticator; use crate::error::VResult; use crate::options::Options; use crate::vaas::Vaas; +use reqwest::Url; /// Builder struct to create a new Vaas instance with the expected default values. /// ```rust /// // Create a new [Vaas] instance from the builder. /// # fn main() -> vaas::error::VResult<()> { /// use vaas::Builder; +/// use vaas::auth::authenticators::ClientCredentials; +/// +/// let authenticator = ClientCredentials::new("client_id".to_string(), "client_secret".to_string()); /// -/// let vaas = Builder::new(String::from("mytoken")).build()?; +/// let vaas = Builder::new(authenticator).build()?; /// # Ok(()) } /// ``` -pub struct Builder { - token: String, +pub struct Builder { + authenticator: A, url: Url, options: Options, } -impl Builder { +impl Builder { /// Create a new VaasBuilder to create a [Vaas] instance. - pub fn new(token: String) -> Self { + pub fn new(authenticator: A) -> Self { + use std::str::FromStr; Self { - token, - ..Self::default() + options: Options { + keep_alive_delay_ms: 10_000, + keep_alive: true, + channel_capacity: 100, + }, + authenticator, + url: Url::from_str("wss://gateway.production.vaas.gdatasecurity.de").unwrap(), } } @@ -72,26 +82,11 @@ impl Builder { } /// Create a [Vaas] struct from the `VaasBuilder`. - pub fn build(self) -> VResult { + pub fn build(self) -> VResult> { Ok(Vaas { options: self.options, - token: self.token, + authenticator: self.authenticator, url: self.url, }) } } - -impl Default for Builder { - fn default() -> Self { - use std::str::FromStr; - Self { - options: Options { - keep_alive_delay_ms: 10_000, - keep_alive: true, - channel_capacity: 100, - }, - token: String::new(), - url: Url::from_str("wss://gateway.production.vaas.gdatasecurity.de").unwrap(), - } - } -} diff --git a/rust/src/c b/rust/src/c new file mode 100644 index 00000000..e69de29b diff --git a/rust/src/connection.rs b/rust/src/connection.rs index a0d47a57..5e152efb 100644 --- a/rust/src/connection.rs +++ b/rust/src/connection.rs @@ -83,8 +83,13 @@ impl Connection { } /// Request a verdict for files behind a list of URLs. - pub async fn for_url_list(&self, url_list: &[Url], ct: &CancellationToken) -> Vec> { - let req = url_list.iter() + pub async fn for_url_list( + &self, + url_list: &[Url], + ct: &CancellationToken, + ) -> Vec> { + let req = url_list + .iter() .map(|url| self.for_url(url, ct)) .collect::>(); @@ -259,10 +264,10 @@ impl Connection { match Self::parse_frame(frame) { Ok(MessageType::VerdictResponse(vr)) => { result_channel.send(Ok(vr))?; - }, + } Ok(MessageType::Close) => { result_channel.send(Err(Error::ConnectionClosed))?; - }, + } Err(e) => { result_channel.send(Err(e))?; } diff --git a/rust/src/error.rs b/rust/src/error.rs index ef72b97f..34e41eeb 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -75,7 +75,7 @@ pub enum Error { NoSessionIdInAuthResp, /// Connection was closed, reconnect is necessary #[error("Connection was closed")] - ConnectionClosed + ConnectionClosed, } impl From>> for Error { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 1df61c0c..7925e4f6 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -17,6 +17,7 @@ //! Check a file hash for malicious content: //! ```rust,no_run //! use vaas::{error::VResult, CancellationToken, Vaas, VaasVerdict, Sha256}; +//! use vaas::auth::authenticators::ClientCredentials; //! use std::convert::TryFrom; //! use std::time::Duration; //! @@ -26,8 +27,8 @@ //! let ct = CancellationToken::from_seconds(10); //! //! //Authenticate and create VaaS instance -//! let token = Vaas::get_token("client_id", "client_secret").await?; -//! let vaas = Vaas::builder(token.into()).build()?.connect().await?; +//! let authenticator = ClientCredentials::new("client_id".to_string(), "client_secret".to_string()); +//! let vaas = Vaas::builder(authenticator).build()?.connect().await?; //! //! // Create the SHA256 we want to check. //! let sha256 = Sha256::try_from("698CDA840A0B344639F0C5DBD5C629A847A27448A9A179CB6B7A648BC1186F23")?; @@ -43,6 +44,7 @@ //! Check a file for malicious content: //! ```rust,no_run //! use vaas::{error::VResult, CancellationToken, Vaas, VaasVerdict}; +//! use vaas::auth::authenticators::ClientCredentials; //! use std::convert::TryFrom; //! use std::time::Duration; //! @@ -52,8 +54,8 @@ //! let ct = CancellationToken::from_seconds(10); //! //! //Authenticate and create VaaS instance -//! let token = Vaas::get_token("client_id", "client_secret").await?; -//! let vaas = Vaas::builder(token.into()).build()?.connect().await?; +//! let authenticator = ClientCredentials::new("client_id".to_string(), "client_secret".to_string()); +//! let vaas = Vaas::builder(authenticator).build()?.connect().await?; //! //! // Create file we want to check. //! let file = std::path::PathBuf::from("myfile"); @@ -69,6 +71,7 @@ //! Check a file behind a URL for malicious content: //! ```rust,no_run //! use vaas::{error::VResult, CancellationToken, Vaas, VaasVerdict}; +//! use vaas::auth::authenticators::ClientCredentials; //! use reqwest::Url; //! use std::convert::TryFrom; //! use std::time::Duration; @@ -79,8 +82,8 @@ //! let ct = CancellationToken::from_seconds(10); //! //! //Authenticate and create VaaS instance -//! let token = Vaas::get_token("client_id", "client_secret").await?; -//! let vaas = Vaas::builder(token.into()).build()?.connect().await?; +//! let authenticator = ClientCredentials::new("client_id".to_string(), "client_secret".to_string()); +//! let vaas = Vaas::builder(authenticator).build()?.connect().await?; //! //! let url = Url::parse("https://mytesturl.test").unwrap(); //! let verdict = vaas.for_url(&url, &ct).await?; @@ -91,39 +94,15 @@ //! } //! ``` //! -//! Check a file behind a URL for malicious content: -//! ```rust,no_run -//! use vaas::{error::VResult, CancellationToken, Vaas, VaasVerdict}; -//! use reqwest::Url; -//! use std::convert::TryFrom; -//! use std::time::Duration; -//! -//! #[tokio::main] -//! async fn main() -> VResult<()> { -//! // Cancel the request after 10 seconds if no response is received. -//! let ct = CancellationToken::from_seconds(10); -//! -//! //Authenticate and create VaaS instance -//! let token = Vaas::get_token("client_id", "client_secret").await?; -//! let vaas = Vaas::builder(token.into()).build()?.connect().await?; -//! -//! let url = Url::parse("https://mytesturl.test").unwrap(); -//! let response = vaas.for_url(&url, &ct).await; -//! -//! // Prints "Clean", "Pup" or "Malicious" -//! println!("{}", response.as_ref().unwrap().verdict); -//! Ok(()) -//! } #![warn(missing_docs)] -mod authenticator; +pub mod auth; pub mod builder; pub mod cancellation; pub mod connection; pub mod error; pub mod message; mod options; -mod resource_owner_password_grant_authenticator; pub mod sha256; pub mod vaas; pub mod vaas_verdict; diff --git a/rust/src/message/message_type.rs b/rust/src/message/message_type.rs index 0318cfc1..89202d24 100644 --- a/rust/src/message/message_type.rs +++ b/rust/src/message/message_type.rs @@ -24,7 +24,6 @@ impl TryFrom<&String> for MessageType { } } - #[cfg(test)] mod tests { use super::*; @@ -40,13 +39,14 @@ mod tests { "url": null, "upload_token": null } - "#.to_string(); + "# + .to_string(); let message_type = MessageType::try_from(&msg).unwrap(); let is_correct_type = match message_type { MessageType::VerdictResponse(_) => true, - _ => false + _ => false, }; assert!(is_correct_type); @@ -60,15 +60,16 @@ mod tests { "type": "UniqueErrorType", "text": "Something went wrong..." } - "#.to_string(); + "# + .to_string(); let message_type = MessageType::try_from(&msg); let is_correct_type = match message_type { Err(Error::ErrorResponse(_)) => true, - _ => false + _ => false, }; assert!(is_correct_type); } -} \ No newline at end of file +} diff --git a/rust/src/message/verdict.rs b/rust/src/message/verdict.rs index 04d50a48..f4b4c507 100644 --- a/rust/src/message/verdict.rs +++ b/rust/src/message/verdict.rs @@ -60,4 +60,4 @@ impl TryFrom<&str> for Verdict { v => Err(Error::InvalidVerdict(v.to_string())), } } -} \ No newline at end of file +} diff --git a/rust/src/options.rs b/rust/src/options.rs index b9ff85a9..2b873e0c 100644 --- a/rust/src/options.rs +++ b/rust/src/options.rs @@ -1,4 +1,3 @@ - #[derive(Debug, Clone)] pub(crate) struct Options { pub keep_alive_delay_ms: u64, diff --git a/rust/src/resource_owner_password_grant_authenticator.rs b/rust/src/resource_owner_password_grant_authenticator.rs deleted file mode 100644 index 9463edd0..00000000 --- a/rust/src/resource_owner_password_grant_authenticator.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::error::{Error, VResult}; -use crate::{authenticator::Authenticator, message::OpenIdConnectTokenResponse}; -use async_trait::async_trait; -use reqwest::{StatusCode, Url}; - -pub(crate) struct ResourceOwnerPasswordGrantAuthenticator { - client_id: String, - user_name: String, - password: String, - token_url: Url, -} - -impl ResourceOwnerPasswordGrantAuthenticator { - pub fn new(client_id: &str, user_name: &str, password: &str, token_url: &Url) -> Self { - Self { - client_id: client_id.to_string(), - user_name: user_name.to_string(), - password: password.to_string(), - token_url: token_url.clone(), - } - } -} - -#[async_trait] -impl Authenticator for ResourceOwnerPasswordGrantAuthenticator { - async fn get_token(self) -> VResult { - let params = [ - ("client_id", self.client_id.as_str()), - ("username", self.user_name.as_str()), - ("password", self.password.as_str()), - ("grant_type", "password"), - ]; - let client = reqwest::Client::new(); - let token_response = client.post(self.token_url).form(¶ms).send().await?; - - match token_response.status() { - StatusCode::OK => { - let json_string = token_response.text().await?; - Ok(OpenIdConnectTokenResponse::try_from(&json_string)?.access_token) - } - status => Err(Error::FailedAuthTokenRequest( - status, - token_response.text().await.unwrap_or_default(), - )), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn get_token_returns_token() { - let token_url: Url = dotenv::var("TOKEN_URL") - .expect("No TOKEN_URL environment variable set to be used in the integration tests") - .parse() - .expect("Failed to parse TOKEN_URL environment variable"); - let client_id = dotenv::var("VAAS_CLIENT_ID") - .expect("No CLIENT_ID environment variable set to be used in the integration tests"); - let user_name = dotenv::var("VAAS_USER_NAME").expect( - "No VAAS_USER_NAME environment variable set to be used in the integration tests", - ); - let password = dotenv::var("VAAS_PASSWORD").expect( - "No VAAS_PASSWORD environment variable set to be used in the integration tests", - ); - let authenticator = ResourceOwnerPasswordGrantAuthenticator::new( - client_id.as_str(), - user_name.as_str(), - password.as_str(), - &token_url, - ); - let token = authenticator.get_token().await; - - assert!(token.is_ok()) - } -} diff --git a/rust/src/vaas.rs b/rust/src/vaas.rs index 32893651..28c2df56 100644 --- a/rust/src/vaas.rs +++ b/rust/src/vaas.rs @@ -1,95 +1,26 @@ //! The `Vaas` module provides all needed functions to check a hash or file for malicious content. -use crate::authenticator::Authenticator; +use crate::auth::Authenticator; use crate::builder::Builder; use crate::connection::Connection; use crate::error::{Error, VResult}; -use crate::message::{AuthRequest, AuthResponse, OpenIdConnectTokenResponse}; +use crate::message::{AuthRequest, AuthResponse}; use crate::options::Options; -use crate::resource_owner_password_grant_authenticator::ResourceOwnerPasswordGrantAuthenticator; -use reqwest::{StatusCode, Url}; +use reqwest::Url; use websockets::{Frame, WebSocket, WebSocketReadHalf, WebSocketWriteHalf}; /// Provides all functionality needed to check a hash or file for malicious content. #[derive(Debug, Clone)] -pub struct Vaas { - pub(super) token: String, +pub struct Vaas { + pub(super) authenticator: A, pub(super) url: Url, pub(super) options: Options, } -impl Vaas { - /// Get an OpenID Connect token to use for authentication from a custom authentication provider URL. - pub async fn get_token_from_url( - client_id: &str, - client_secret: &str, - token_endpoint: Url, - ) -> VResult { - let params = [ - ("client_id", client_id), - ("client_secret", client_secret), - ("grant_type", "client_credentials"), - ]; - let client = reqwest::Client::new(); - let token_response = client.post(token_endpoint).form(¶ms).send().await?; - - match token_response.status() { - StatusCode::OK => { - let json_string = token_response.text().await?; - Ok(OpenIdConnectTokenResponse::try_from(&json_string)?.access_token) - } - status => Err(Error::FailedAuthTokenRequest( - status, - token_response.text().await.unwrap_or_default(), - )), - } - } - - /// Get an OpenID Connect token to use for authentication from the default authentication provider. - pub async fn get_token(client_id: &str, client_secret: &str) -> VResult { - let default_auth_url = Url::parse( - "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token", - ) - .unwrap(); - Vaas::get_token_from_url(client_id, client_secret, default_auth_url).await - } - - /// Get an ID token from the default identity provider. Use this if you have a - /// user name and password as credentials. - pub async fn get_token_with_user_name_and_password( - client_id: &str, - user_name: &str, - password: &str, - ) -> VResult { - let default_auth_url = Url::parse( - "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token", - ) - .unwrap(); - Self::get_token_with_user_name_and_password_from_url( - client_id, - user_name, - password, - &default_auth_url, - ) - .await - } - - /// Get an ID token from an identity provider. Use this if you have a - /// user name and password as credentials. - pub async fn get_token_with_user_name_and_password_from_url( - client_id: &str, - user_name: &str, - password: &str, - token_url: &Url, - ) -> VResult { - let authenticator = - ResourceOwnerPasswordGrantAuthenticator::new(client_id, user_name, password, token_url); - authenticator.get_token().await - } - +impl Vaas { /// Create a new [Builder] instance to configure the `Vaas` instance. - pub fn builder(token: String) -> Builder { - Builder::new(token) + pub fn builder(authenticator: A) -> Builder { + Builder::new(authenticator) } /// Connect to the server endpoints to request a verdict for a hash or file. @@ -113,7 +44,8 @@ impl Vaas { ws_reader: &mut WebSocketReadHalf, ws_writer: &mut WebSocketWriteHalf, ) -> VResult { - let auth_request = AuthRequest::new(self.token.clone(), None).to_json()?; + let token = self.authenticator.get_token().await?; + let auth_request = AuthRequest::new(token, None).to_json()?; ws_writer.send_text(auth_request).await?; let frame = ws_reader.receive().await?; diff --git a/rust/tests/real_api_integration_tests.rs b/rust/tests/real_api_integration_tests.rs index 65ac0e28..83b69b26 100644 --- a/rust/tests/real_api_integration_tests.rs +++ b/rust/tests/real_api_integration_tests.rs @@ -3,7 +3,7 @@ use rand::{distributions::Alphanumeric, Rng}; use reqwest::Url; use std::convert::TryFrom; use std::ops::Deref; -use vaas::error::Error::FailedAuthTokenRequest; +use vaas::auth::authenticators::{ClientCredentials, Password}; use vaas::{message::Verdict, CancellationToken, Connection, Sha256, Vaas}; async fn get_vaas() -> Connection { @@ -15,12 +15,10 @@ async fn get_vaas() -> Connection { .expect("No CLIENT_ID environment variable set to be used in the integration tests"); let client_secret = dotenv::var("CLIENT_SECRET") .expect("No CLIENT_SECRET environment variable set to be used in the integration tests"); - let token = Vaas::get_token_from_url(&client_id, &client_secret, token_url) - .await - .unwrap(); + let authenticator = ClientCredentials::new(client_id, client_secret).with_token_url(token_url); let vaas_url = dotenv::var("VAAS_URL") .expect("No VAAS_URL environment variable set to be used in the integration tests"); - Vaas::builder(token) + Vaas::builder(authenticator) .url(Url::parse(&vaas_url).unwrap()) .build() .unwrap() @@ -30,7 +28,7 @@ async fn get_vaas() -> Connection { } #[tokio::test] -async fn connect_with_resource_owner_password_grant() { +async fn connect_with_username_and_password() { let token_url: Url = dotenv::var("TOKEN_URL") .expect("No TOKEN_URL environment variable set to be used in the integration tests") .parse() @@ -43,15 +41,8 @@ async fn connect_with_resource_owner_password_grant() { .expect("No VAAS_PASSWORD environment variable set to be used in the integration tests"); let vaas_url = dotenv::var("VAAS_URL") .expect("No VAAS_URL environment variable set to be used in the integration tests"); - let token = Vaas::get_token_with_user_name_and_password_from_url( - client_id.as_str(), - user_name.as_str(), - password.as_str(), - &token_url, - ) - .await - .unwrap(); - let connection = Vaas::builder(token) + let authenticator = Password::new(client_id, user_name, password).with_token_url(token_url); + let connection = Vaas::builder(authenticator) .url(Url::parse(&vaas_url).unwrap()) .build() .unwrap() @@ -61,24 +52,6 @@ async fn connect_with_resource_owner_password_grant() { assert!(connection.is_ok()) } -#[tokio::test] -async fn from_sha256_wrong_credentials() { - let token_url: Url = dotenv::var("TOKEN_URL") - .expect("No TOKEN_URL environment variable set to be used in the integration tests") - .parse() - .expect("Failed to parse TOKEN_URL environment variable"); - let client_id = "invalid"; - let client_secret = "invalid"; - let token = Vaas::get_token_from_url(&client_id, &client_secret, token_url).await; - - assert!(token.is_err()); - assert!(match token { - Ok(_) => false, - Err(FailedAuthTokenRequest(_, _)) => true, - _ => false, - }) -} - #[tokio::test] async fn from_sha256_list_multiple_hashes() { let vaas = get_vaas().await;