diff options
Diffstat (limited to '')
| -rw-r--r-- | crates/secd/src/auth/n.rs | 91 | ||||
| -rw-r--r-- | crates/secd/src/auth/z/mod.rs | 3 | ||||
| -rw-r--r-- | crates/secd/src/client/store/mod.rs | 53 | ||||
| -rw-r--r-- | crates/secd/src/client/store/sql_db.rs | 70 | ||||
| -rw-r--r-- | crates/secd/src/lib.rs | 85 |
5 files changed, 252 insertions, 50 deletions
diff --git a/crates/secd/src/auth/n.rs b/crates/secd/src/auth/n.rs index dde6e7d..12a5411 100644 --- a/crates/secd/src/auth/n.rs +++ b/crates/secd/src/auth/n.rs @@ -5,12 +5,13 @@ use crate::{ DEFAULT_SIGNUP_EMAIL, }, store::{ - AddressLens, AddressValidationLens, CredentialLens, IdentityLens, Storable, StoreError, + AddressLens, AddressValidationLens, CredentialLens, IdentityLens, ImpersonatorLens, + Storable, StoreError, }, }, util::{self, ErrorContext}, Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod, - Credential, CredentialId, CredentialType, Identity, IdentityId, Secd, SecdError, + Credential, CredentialId, CredentialType, Identity, IdentityId, Impersonator, Secd, SecdError, ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS, ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION, }; @@ -19,6 +20,7 @@ use log::warn; use rand::Rng; use std::str::FromStr; use time::{Duration, OffsetDateTime}; +use tokio::join; use uuid::Uuid; impl Secd { @@ -363,13 +365,13 @@ impl Secd { Ok(identity) } - pub async fn validate_credential(&self, t: CredentialType) -> Result<Credential, SecdError> { + pub async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError> { let mut retrieved = Credential::find( self.store.clone(), &CredentialLens { id: None, identity_id: None, - t: Some(&t), + t: Some(t), }, ) .await? @@ -396,7 +398,7 @@ impl Secd { retrieved.hash_compare(&t, &self.crypter)?; // Return the initially provided plaintext credential since it's valid - retrieved.t = t; + retrieved.t = t.clone(); Ok(retrieved) } @@ -503,4 +505,83 @@ impl Secd { credential.write(self.store.clone()).await?; Ok(()) } + + pub async fn impersonate( + &self, + impersonator_id: &IdentityId, + target_id: &IdentityId, + ) -> Result<Credential, SecdError> { + let impersonator_lens = IdentityLens { + id: Some(&impersonator_id), + address_type: None, + validated_address: None, + }; + let target_lens = IdentityLens { + id: Some(&target_id), + address_type: None, + validated_address: None, + }; + let (i, t) = join!( + Identity::find(self.store.clone(), &impersonator_lens,), + Identity::find(self.store.clone(), &target_lens,) + ); + + let (i, t) = ( + i.ctx("failed to retrieve impersonator identity")?, + t.ctx("failed to retrieve target identity")?, + ); + if i.is_empty() || t.is_empty() { + return Err(SecdError::IdentityNotFound) + .ctx("failed to retrieve impersonator or target identity for impersonation"); + } + + let existing_impersonation = Impersonator::find( + self.store.clone(), + &ImpersonatorLens { + impersonator_id: Some(impersonator_id), + target_id: Some(target_id), + }, + ) + .await + .ctx("failed to find existing impersonation")?; + + // TODO: We could expire the session chain, but I think we want to handle this more intelligently. + // For now, just revoke the credential manually...pita I know... + if !existing_impersonation.is_empty() { + return Err(SecdError::ImpersonatorAlreadyExists) + .ctx("Target already being impersonated by the provided impersonator identity"); + } + + let new_identity = self + .create_credential( + Credential::new_session(*target_id)?.t, + Some(*target_id), + OffsetDateTime::now_utc().checked_add(Duration::minutes(30)), + ) + .await + .ctx("failed to create new credential for target identity")?; + + let new_session = new_identity + .new_credentials + .iter() + .next() + .ok_or(SecdError::InvalidCredential) + .ctx("failed to retrieve new session from newly created target credential")? + .clone(); + + Impersonator { + impersonator: i + .into_iter() + .next() + .ok_or(SecdError::IdentityNotFound) + .ctx("failed to find impersonator identity")?, + target: new_identity, + created_at: OffsetDateTime::now_utc(), + } + .write(self.store.clone()) + .await + .ctx("failed to write new impersonator")?; + + Ok(new_session) + } } diff --git a/crates/secd/src/auth/z/mod.rs b/crates/secd/src/auth/z/mod.rs index d64f674..bde319a 100644 --- a/crates/secd/src/auth/z/mod.rs +++ b/crates/secd/src/auth/z/mod.rs @@ -49,9 +49,6 @@ impl Authorization for Secd { .map(|e| Uuid::parse_str(e).unwrap()) .collect()) } - async fn check_list_subjects(&self) -> Result<Vec<i32>, SecdError> { - unimplemented!() - } async fn write(&self, ts: &[Relationship]) -> Result<(), SecdError> { let spice = self .spice diff --git a/crates/secd/src/client/store/mod.rs b/crates/secd/src/client/store/mod.rs index 6c42dba..f08aa41 100644 --- a/crates/secd/src/client/store/mod.rs +++ b/crates/secd/src/client/store/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod sql_db; +pub mod sql_db; use async_trait::async_trait; use sqlx::{Postgres, Sqlite}; @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::{ Address, AddressType, AddressValidation, Credential, CredentialId, CredentialType, Identity, - IdentityId, + IdentityId, Impersonator, }; use self::sql_db::SqlClient; @@ -19,6 +19,7 @@ pub enum StoreError { ParseError(#[from] strum::ParseError), StoreValueCannotBeParsedInvariant, IdempotentCheckAlreadyExists, + ExpectedEntity, } #[async_trait] @@ -32,7 +33,7 @@ pub enum StoreType { } #[async_trait] -pub(crate) trait Storable<'a> { +pub trait Storable<'a> { type Item; type Lens; @@ -43,33 +44,39 @@ pub(crate) trait Storable<'a> { ) -> Result<Vec<Self::Item>, StoreError>; } -pub(crate) trait Lens {} +pub trait Lens {} -pub(crate) struct AddressLens<'a> { +pub struct AddressLens<'a> { pub id: Option<&'a Uuid>, pub t: Option<&'a AddressType>, } impl<'a> Lens for AddressLens<'a> {} -pub(crate) struct AddressValidationLens<'a> { +pub struct AddressValidationLens<'a> { pub id: Option<&'a Uuid>, } impl<'a> Lens for AddressValidationLens<'a> {} -pub(crate) struct IdentityLens<'a> { +pub struct IdentityLens<'a> { pub id: Option<&'a Uuid>, pub address_type: Option<&'a AddressType>, pub validated_address: Option<bool>, } impl<'a> Lens for IdentityLens<'a> {} -pub(crate) struct CredentialLens<'a> { +pub struct CredentialLens<'a> { pub id: Option<CredentialId>, pub identity_id: Option<IdentityId>, pub t: Option<&'a CredentialType>, } impl<'a> Lens for CredentialLens<'a> {} +pub struct ImpersonatorLens<'a> { + pub impersonator_id: Option<&'a IdentityId>, + pub target_id: Option<&'a IdentityId>, +} +impl<'a> Lens for ImpersonatorLens<'a> {} + #[async_trait] impl<'a> Storable<'a> for Address { type Item = Address; @@ -179,3 +186,33 @@ impl<'a> Storable<'a> for Credential { }) } } + +#[async_trait] +impl<'a> Storable<'a> for Impersonator { + type Item = Impersonator; + type Lens = ImpersonatorLens<'a>; + + async fn write(&self, store: Arc<dyn Store>) -> Result<(), StoreError> { + match store.get_type() { + StoreType::Postgres { c } => c.write_impersonator(self).await?, + StoreType::Sqlite { c } => c.write_impersonator(self).await?, + } + Ok(()) + } + + async fn find( + store: Arc<dyn Store>, + lens: &'a Self::Lens, + ) -> Result<Vec<Self::Item>, StoreError> { + Ok(match store.get_type() { + StoreType::Postgres { c } => { + c.find_impersonator(lens.impersonator_id, lens.target_id) + .await? + } + StoreType::Sqlite { c } => { + c.find_impersonator(lens.impersonator_id, lens.target_id) + .await? + } + }) + } +} diff --git a/crates/secd/src/client/store/sql_db.rs b/crates/secd/src/client/store/sql_db.rs index 7b3a68e..5777704 100644 --- a/crates/secd/src/client/store/sql_db.rs +++ b/crates/secd/src/client/store/sql_db.rs @@ -1,4 +1,5 @@ use super::{Store, StoreError, StoreType}; +use crate::Impersonator; use crate::{ util::ErrorContext, Address, AddressType, AddressValidation, AddressValidationMethod, Credential, CredentialId, CredentialType, Identity, IdentityId, @@ -26,6 +27,8 @@ const WRITE_CREDENTIAL: &str = "write_credential"; const FIND_CREDENTIAL: &str = "find_credential"; const WRITE_IDENTITY: &str = "write_identity"; const FIND_IDENTITY: &str = "find_identity"; +const WRITE_IMPERSONATOR: &str = "write_impersonator"; +const FIND_IMPERSONATOR: &str = "find_impersonator"; const ERR_MSG_MIGRATION_FAILED: &str = "Failed to apply secd migrations to a sql db. File a bug at https://www.github.com/branchcontrol/secdiam"; @@ -64,6 +67,14 @@ lazy_static! { FIND_CREDENTIAL, include_str!("../../../store/sqlite/sql/find_credential.sql"), ), + ( + WRITE_IMPERSONATOR, + include_str!("../../../store/sqlite/sql/write_impersonator.sql"), + ), + ( + FIND_IMPERSONATOR, + include_str!("../../../store/sqlite/sql/find_impersonator.sql"), + ), ] .iter() .cloned() @@ -102,6 +113,14 @@ lazy_static! { FIND_CREDENTIAL, include_str!("../../../store/pg/sql/find_credential.sql"), ), + ( + WRITE_IMPERSONATOR, + include_str!("../../../store/pg/sql/write_impersonator.sql"), + ), + ( + FIND_IMPERSONATOR, + include_str!("../../../store/pg/sql/find_impersonator.sql"), + ), ] .iter() .cloned() @@ -525,6 +544,57 @@ where Ok(res) } + + pub async fn write_impersonator(&self, i: &Impersonator) -> Result<(), StoreError> { + let sqls = get_sqls(&self.sqls_root, WRITE_IMPERSONATOR); + sqlx::query(&sqls[0]) + .bind(i.impersonator.id) + .bind(i.target.id) + .bind(i.target.new_credentials.get(0).map(|e| &e.id)) + .bind(i.created_at) + .fetch_all(&self.pool) + .await + .extend_err()?; + Ok(()) + } + pub async fn find_impersonator( + &self, + impersonator_id: Option<&Uuid>, + target_id: Option<&Uuid>, + ) -> Result<Vec<Impersonator>, StoreError> { + let sqls = get_sqls(&self.sqls_root, FIND_IMPERSONATOR); + let rs = sqlx::query_as::<_, (Uuid, Uuid, OffsetDateTime)>(&sqls[0]) + .bind(impersonator_id) + .bind(target_id) + .bind(OffsetDateTime::now_utc()) + .fetch_all(&self.pool) + .await + .extend_err()?; + + let mut res = vec![]; + for (impersonator_id, target_id, created_at) in rs.into_iter() { + let impersonator = self + .find_identity(Some(&impersonator_id), None, None) + .await? + .into_iter() + .next() + .ok_or(StoreError::ExpectedEntity)?; + let target = self + .find_identity(Some(&target_id), None, None) + .await? + .into_iter() + .next() + .ok_or(StoreError::ExpectedEntity)?; + + res.push(Impersonator { + impersonator, + target, + created_at, + }) + } + + Ok(res) + } } fn get_sqls(root: &str, file: &str) -> Vec<String> { diff --git a/crates/secd/src/lib.rs b/crates/secd/src/lib.rs index c84ce44..7fa1755 100644 --- a/crates/secd/src/lib.rs +++ b/crates/secd/src/lib.rs @@ -41,9 +41,7 @@ pub type AddressId = Uuid; pub type AddressValidationId = Uuid; pub type CredentialId = Uuid; pub type IdentityId = Uuid; -pub type MotifId = Uuid; pub type PhoneNumber = String; -pub type RefId = Uuid; #[derive(Debug, derive_more::Display, thiserror::Error)] pub enum SecdError { @@ -60,10 +58,15 @@ pub enum SecdError { CrypterError(#[from] CrypterError), + CfgMissingSpiceSecret, + CfgMissingSpiceServer, + TooManyIdentities, IdentityNotFound, IdentityAlreadyExists, + ImpersonatorAlreadyExists, + EmailMessengerError(#[from] EmailMessengerError), InvalidEmaillAddress(#[from] email_address::Error), @@ -103,43 +106,40 @@ struct Cfg { email_signup_message_asset_loc: Option<String>, email_signin_message: Option<String>, email_signup_message: Option<String>, - spice_secret: String, - spice_server: String, + spice_secret: Option<String>, + spice_server: Option<String>, } #[async_trait] pub trait Authentication { - async fn validate_address( - &self, - address_type: AddressType, - identity_id: Option<IdentityId>, - ) -> Result<AddressValidation, SecdError>; - - async fn complete_address_validation( - &self, - validation_id: &AddressValidationId, - plaintext_token: Option<String>, - plaintext_code: Option<String>, - ) -> Result<AddressValidation, SecdError>; - + async fn check_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>; async fn create_credential( &self, t: &CredentialType, identity_id: Option<IdentityId>, - ) -> Result<IdentityId, SecdError>; - // async fn update_credential(&self, t: &CredentialType) -> Result<(), SecdError>; - async fn reset_credential( + expires_at: Option<OffsetDateTime>, + ) -> Result<Identity, SecdError>; + async fn create_identity( &self, + i: &Identity, t: &CredentialType, - address: &AddressType, + md: Option<String>, + ) -> Result<Identity, SecdError>; + async fn impersonate( + &self, + impersonator: &Identity, + target: &Identity, ) -> Result<Credential, SecdError>; - async fn validate_credential(&self, t: &CredentialType) -> Result<Credential, SecdError>; - - // async fn expire_session_chain(&self, t: &SessionToken) -> Result<(), SecdError>; - // async fn expire_sessions(&self, i: &IdentityId) -> Result<(), SecdError>; - + async fn revoke_credential(&self, credential_id: &CredentialId) -> Result<Identity, SecdError>; + async fn send_address_validation(&self, t: AddressType) + -> Result<AddressValidation, SecdError>; + async fn validate_address( + &self, + v_id: &AddressValidationId, + plaintext_token: Option<String>, + plaintext_code: Option<String>, + ) -> Result<AddressValidation, SecdError>; // async fn get_identity(&self, t: &SessionToken) -> Result<Identity, SecdError>; - // async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError>; } #[async_trait] @@ -151,7 +151,6 @@ pub trait Authorization { subj: &Subject, relation: &Relation, ) -> Result<Vec<Uuid>, SecdError>; - async fn check_list_subjects(&self) -> Result<Vec<i32>, SecdError>; async fn write(&self, relationships: &[Relationship]) -> Result<(), SecdError>; } @@ -218,7 +217,7 @@ pub enum AddressType { } #[serde_as] -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct Credential { pub id: CredentialId, pub identity_id: IdentityId, @@ -263,6 +262,15 @@ pub struct Identity { pub deleted_at: Option<OffsetDateTime>, } +#[serde_with::skip_serializing_none] +#[derive(Debug, Serialize)] +pub struct Impersonator { + pub impersonator: Identity, + pub target: Identity, + #[serde(with = "time::serde::timestamp")] + pub created_at: OffsetDateTime, +} + impl Cfg { fn resolve(&mut self) -> Result<(), SecdError> { if let Some(path) = &self.email_signin_message_asset_loc { @@ -313,8 +321,8 @@ impl Secd { CRYPTER_SECRET_KEY_DEFAULT.to_string() }); - info!("starting client with auth_store: {:?}", auth_store); - info!("starting client with email_messenger: {:?}", auth_store); + info!("init with auth_store: {:?}", auth_store); + info!("init with email_messenger: {:?}", email_messenger); let store = match auth_store { AuthStore::Sqlite { conn } => { @@ -340,7 +348,7 @@ impl Secd { .connect(&conn) .await .map_err(|e| { - SecdError::StoreInitFailure(format!("failed to init sqlite: {}", e)) + SecdError::StoreInitFailure(format!("failed to init postgres: {}", e)) })?, ) .await @@ -366,8 +374,17 @@ impl Secd { let spice = match z_schema { Some(schema) => { - let c: Arc<Spice> = - Arc::new(Spice::new(cfg.spice_secret.clone(), cfg.spice_server.clone()).await); + let c: Arc<Spice> = Arc::new( + Spice::new( + cfg.spice_secret + .clone() + .ok_or(SecdError::CfgMissingSpiceSecret)?, + cfg.spice_server + .clone() + .ok_or(SecdError::CfgMissingSpiceServer)?, + ) + .await, + ); c.write_schema(schema) .await .unwrap_or_else(|_| panic!("{}", "failed to write authorization schema")); |
