diff --git a/Cargo.lock b/Cargo.lock index 08f93bf..95b1f79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,7 @@ dependencies = [ "log", "opentelemetry-datadog", "rstest", + "serde", "serde_json", "sha2", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 9d7feda..739cf3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ flate2 = "1.0" base64 = "0.22.1" hmac = "0.12.1" sha2 = "0.10.8" +serde = { version = "1.0.207", features = ["derive"] } [dev-dependencies] rstest = "0.22.0" diff --git a/src/http/urls.rs b/src/http/urls.rs index d99a231..15d1914 100644 --- a/src/http/urls.rs +++ b/src/http/urls.rs @@ -1,7 +1,11 @@ +use crate::models::external::identity::{ExternalIdentity, Policy, PolicyAttachment}; use crate::models::external::identity_provider::ExternalIdentityProvider; use crate::models::external::token::ExternalToken; +use crate::services::base::upsert_repository::{ + IdentityRepository, PolicyAttachmentRepository, PolicyRepository, +}; use crate::services::token_service::{TokenProvider, TokenService}; -use actix_web::{error, get, web, HttpRequest}; +use actix_web::{delete, error, get, post, web, HttpRequest, HttpResponse, Responder}; use log::error; use std::sync::Arc; @@ -27,3 +31,123 @@ pub async fn token( None => Err(error::ErrorUnauthorized("No Authorization header found")), } } + +#[post("/policy/{id}")] +pub async fn post_policy( + id: web::Path, + policy: String, + data: web::Data>, +) -> actix_web::Result { + data.upsert(id.to_string(), Policy::new(policy)) + .await + .map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(HttpResponse::Ok().finish()) +} + +#[get("/policy/{id}")] +pub async fn get_policy( + id: web::Path, + data: web::Data>, +) -> actix_web::Result { + let policy = data.get(id.to_string()).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(policy.content) +} + +#[delete("/policy/{id}")] +pub async fn delete_policy( + id: web::Path, + data: web::Data>, +) -> actix_web::Result { + data.delete(id.to_string()).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(HttpResponse::Ok().finish()) +} + +#[post("/identity/{identity_provider}/{id}")] +pub async fn post_identity( + params: web::Path<(String, String)>, + data: web::Data>, +) -> actix_web::Result { + let key = params.into_inner(); + let eid = ExternalIdentity::from(key.clone()); + data.upsert(key, eid).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(HttpResponse::Ok().finish()) +} + +#[get("/identity/{identity_provider}/{id}")] +pub async fn get_identity( + params: web::Path<(String, String)>, + data: web::Data>, +) -> actix_web::Result { + let eid = data.get(params.into_inner()).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(web::Json(eid)) +} + +#[delete("/identity/{identity_provider}/{id}")] +pub async fn delete_identity( + params: web::Path<(String, String)>, + data: web::Data>, +) -> actix_web::Result { + data.delete(params.into_inner()).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(HttpResponse::Ok().finish()) +} + +#[post("/attachment/{identity_provider}/{id}/{policy_id}")] +pub async fn post_policy_attachment( + params: web::Path<(String, String, String)>, + data: web::Data>, +) -> actix_web::Result { + let (identity_provider, id, policy_id) = params.into_inner(); + let eid = ExternalIdentity::new(id, identity_provider); + let attachment = PolicyAttachment::single(policy_id); + data.upsert(eid, attachment).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(HttpResponse::Ok().finish()) +} + +#[post("/attachment/{identity_provider}/{id}")] +pub async fn get_policy_attachment( + params: web::Path<(String, String)>, + data: web::Data>, +) -> actix_web::Result { + let (identity_provider, id) = params.into_inner(); + let eid = ExternalIdentity::new(id, identity_provider); + let result = data.get(eid).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(web::Json(result)) +} + +#[delete("/attachment/{identity_provider}/{id}")] +pub async fn delete_policy_attachment( + params: web::Path<(String, String)>, + data: web::Data>, +) -> actix_web::Result { + let (identity_provider, id) = params.into_inner(); + let eid = ExternalIdentity::new(id, identity_provider); + data.delete(eid).await.map_err(|e| { + error!("Error: {:?}", e); + error::ErrorInternalServerError("Failed to upsert policy") + })?; + Ok(HttpResponse::Ok().finish()) +} diff --git a/src/main.rs b/src/main.rs index 3c0b4bd..371b2c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,13 @@ mod http; mod models; mod services; -use crate::http::urls::token; +use crate::http::urls::{ + delete_identity, delete_policy, delete_policy_attachment, get_identity, get_policy, + get_policy_attachment, post_identity, post_policy, post_policy_attachment, token, +}; +use crate::services::base::upsert_repository::{ + IdentityRepository, PolicyAttachmentRepository, PolicyRepository, +}; use crate::services::configuration_manager::ConfigurationManager; use crate::services::identity_validator_provider; use crate::services::token_service::TokenService; @@ -25,8 +31,10 @@ async fn main() -> Result<()> { let _ = tokio::spawn(cm.watch_for_identity_providers()); info!("Configuration manager started"); - let policy_repository = Arc::new(RwLock::new(HashMap::new())); - let policy_attachments_repository = Arc::new(RwLock::new(HashMap::new())); + let policy_repository: Arc = Arc::new(RwLock::new(HashMap::new())); + let policy_attachments_repository: Arc = + Arc::new(RwLock::new(HashMap::new())); + let identity_repository: Arc = Arc::new(RwLock::new(HashMap::new())); info!("listening on {}:{}", &addr.0, &addr.1); HttpServer::new(move || { @@ -37,8 +45,25 @@ async fn main() -> Result<()> { Arc::clone(&secret), )); App::new() + // Application services .app_data(web::Data::new(token_provider)) + .app_data(web::Data::new(policy_repository.clone())) + .app_data(web::Data::new(policy_attachments_repository.clone())) + .app_data(web::Data::new(identity_repository.clone())) + // Token endpoint .service(token) + // Policy CRUD + .service(post_policy) + .service(get_policy) + .service(delete_policy) + // Identity CRUD + .service(post_identity) + .service(get_identity) + .service(delete_identity) + // Policy Attachment CRUD + .service(post_policy_attachment) + .service(get_policy_attachment) + .service(delete_policy_attachment) }) .bind(addr)? .run() diff --git a/src/models/external/identity.rs b/src/models/external/identity.rs index d6abe32..1491942 100644 --- a/src/models/external/identity.rs +++ b/src/models/external/identity.rs @@ -1,6 +1,7 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashSet; -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] /// Struct that represents an external identity pub struct ExternalIdentity { /// The user ID extracted from the external identity provider @@ -12,7 +13,7 @@ pub struct ExternalIdentity { impl ExternalIdentity { /// Creates a new instance of an external identity - pub fn new(user_id: String, identity_provider: String) -> Self { + pub fn new(identity_provider: String, user_id: String) -> Self { ExternalIdentity { user_id: user_id.to_lowercase(), identity_provider: identity_provider.to_lowercase(), @@ -20,24 +21,32 @@ impl ExternalIdentity { } } -#[derive(Debug, Clone)] +impl From<(String, String)> for ExternalIdentity { + fn from(value: (String, String)) -> Self { + ExternalIdentity::new(value.0, value.1) + } +} + +#[derive(Debug, Clone, Serialize)] #[allow(dead_code)] pub struct PolicyAttachment { - pub external_identity: ExternalIdentity, pub policies: HashSet, } #[allow(dead_code)] impl PolicyAttachment { - pub fn new(external_identity: ExternalIdentity, policies: HashSet) -> Self { - PolicyAttachment { - external_identity, - policies, - } + pub fn new(policies: HashSet) -> Self { + PolicyAttachment { policies } + } + + pub fn single(policy: String) -> Self { + let mut set = HashSet::new(); + set.insert(policy); + PolicyAttachment { policies: set } } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Policy { pub content: String, } diff --git a/src/services/base/upsert_repository.rs b/src/services/base/upsert_repository.rs index d58746d..885fc7c 100644 --- a/src/services/base/upsert_repository.rs +++ b/src/services/base/upsert_repository.rs @@ -11,10 +11,10 @@ pub trait UpsertRepository { async fn get(&self, key: Key) -> Result; /// Updates or inserts a policy by id - async fn upsert(&mut self, key: Key, entity: Entity) -> Result<(), Self::Error>; + async fn upsert(&self, key: Key, entity: Entity) -> Result<(), Self::Error>; /// Deletes policy by id - async fn delete(&mut self, key: Key) -> Result<(), Self::Error>; + async fn delete(&self, key: Key) -> Result<(), Self::Error>; } #[allow(dead_code)] pub type IdentityRepository = diff --git a/src/services/external_identity_validator.rs b/src/services/external_identity_validator.rs index 98a7252..99f0391 100644 --- a/src/services/external_identity_validator.rs +++ b/src/services/external_identity_validator.rs @@ -63,7 +63,7 @@ fn extract_user_id( ) -> Option { let value = claims.get(user_id_claim)?; let user_id = value.as_str()?.to_owned(); - Some(ExternalIdentity::new(user_id, identity_provider)) + Some(ExternalIdentity::new(identity_provider, user_id)) } #[async_trait] diff --git a/src/services/repositories/in_memory.rs b/src/services/repositories/in_memory.rs index 09330b0..168578f 100644 --- a/src/services/repositories/in_memory.rs +++ b/src/services/repositories/in_memory.rs @@ -20,7 +20,7 @@ impl UpsertRepository } async fn upsert( - &mut self, + &self, key: (String, String), entity: ExternalIdentity, ) -> Result<(), Self::Error> { @@ -29,7 +29,7 @@ impl UpsertRepository Ok(()) } - async fn delete(&mut self, key: (String, String)) -> Result<(), Self::Error> { + async fn delete(&self, key: (String, String)) -> Result<(), Self::Error> { let mut write_guard = self.write().await; (*write_guard).remove(&key); Ok(()) @@ -48,13 +48,13 @@ impl UpsertRepository for RwLock> { } } - async fn upsert(&mut self, key: String, entity: Policy) -> Result<(), Self::Error> { + async fn upsert(&self, key: String, entity: Policy) -> Result<(), Self::Error> { let mut write_guard = self.write().await; (*write_guard).insert(key, entity); Ok(()) } - async fn delete(&mut self, key: String) -> Result<(), Self::Error> { + async fn delete(&self, key: String) -> Result<(), Self::Error> { let mut write_guard = self.write().await; (*write_guard).remove(&key); Ok(()) @@ -76,7 +76,7 @@ impl UpsertRepository } async fn upsert( - &mut self, + &self, key: ExternalIdentity, entity: PolicyAttachment, ) -> Result<(), Self::Error> { @@ -85,7 +85,6 @@ impl UpsertRepository Some(entity) => { let new_policies = entity.policies.union(&entity.policies).cloned().collect(); let new_entity = PolicyAttachment { - external_identity: entity.external_identity.clone(), policies: new_policies, }; (*write_guard).insert(key, new_entity); @@ -97,7 +96,7 @@ impl UpsertRepository Ok(()) } - async fn delete(&mut self, key: ExternalIdentity) -> Result<(), Self::Error> { + async fn delete(&self, key: ExternalIdentity) -> Result<(), Self::Error> { let mut write_guard = self.write().await; (*write_guard).remove(&key); Ok(())