From ed34a5251f13bbded0aa15719887db4924b351eb Mon Sep 17 00:00:00 2001 From: benj Date: Mon, 22 May 2023 15:47:06 -0700 Subject: update credential API to include sessions This change updates the credential API to include sessions as just another credential type. It adds the ApiToken type and enables revocation of credentials. Updates were also made to the Identity API which now includes a list of new credentials added to an Identity. This change also migrates off the hacky ENV configuration paradigm and includes a new config.toml file specified by the SECD_CONFIG_PATH env var. No default is currently provided. Clippy updates and code cleanup. --- crates/secd/src/client/email/mod.rs | 18 ++++-- crates/secd/src/client/spice/mod.rs | 21 +++---- crates/secd/src/client/store/mod.rs | 84 +++------------------------ crates/secd/src/client/store/sql_db.rs | 102 ++++++--------------------------- 4 files changed, 47 insertions(+), 178 deletions(-) (limited to 'crates/secd/src/client') diff --git a/crates/secd/src/client/email/mod.rs b/crates/secd/src/client/email/mod.rs index 7c7b233..9e591ba 100644 --- a/crates/secd/src/client/email/mod.rs +++ b/crates/secd/src/client/email/mod.rs @@ -42,7 +42,7 @@ pub enum MessengerType { pub(crate) struct LocalMailer {} impl LocalMailer { - pub fn new() -> Arc { + pub fn new_ref() -> Arc { warn!("You are using the local mailer, which will not work in production!"); Arc::new(LocalMailer {}) } @@ -59,7 +59,7 @@ pub(crate) struct Sendgrid { pub api_key: String, } impl Sendgrid { - pub fn new(api_key: String) -> Arc { + pub fn new_ref(api_key: String) -> Arc { Arc::new(Sendgrid { api_key }) } } @@ -89,7 +89,7 @@ impl Sendable for EmailValidationMessage { .subject(self.subject.clone()) .multipart(MultiPart::alternative_plain_html( "".to_string(), - String::from(self.body.clone()), + self.body.clone(), ))?; let mailer = lettre::SmtpTransport::unencrypted_localhost(); @@ -135,7 +135,7 @@ pub(crate) fn parse_email_template( validation_secret: Option, validation_code: Option, ) -> Result { - let mut t = template.clone().to_string(); + let mut t = String::from(template); // We do not allow substutions for a variety of reasons, but mainly security ones. // The only things we want to share are those which secd allows. In this case, that // means we only send an email with static content as provided by the filter, except @@ -143,8 +143,14 @@ pub(crate) fn parse_email_template( // present in the email. t = t.replace("{{secd::validation_id}}", &validation_id.to_string()); - validation_secret.map(|secret| t = t.replace("{{secd::validation_secret}}", &secret)); - validation_code.map(|code| t = t.replace("{{secd::validation_code}}", &code)); + + if let Some(secret) = validation_secret { + t = t.replace("{{secd::validation_secret}}", &secret); + } + + if let Some(code) = validation_code { + t = t.replace("{{secd::validation_code}}", &code); + } Ok(t) } diff --git a/crates/secd/src/client/spice/mod.rs b/crates/secd/src/client/spice/mod.rs index d3ca30d..67965d7 100644 --- a/crates/secd/src/client/spice/mod.rs +++ b/crates/secd/src/client/spice/mod.rs @@ -3,6 +3,7 @@ // favor of a light weight solution that leverages the Zanzibar API but disregards the // scaling part. +#[allow(clippy::module_inception)] pub mod spice { tonic::include_proto!("authzed.api.v1"); } @@ -10,7 +11,7 @@ pub mod spice { use spice::permissions_service_client::PermissionsServiceClient; use spice::schema_service_client::SchemaServiceClient; use spice::WriteSchemaRequest; -use std::env::var; +use std::matches; use tonic::metadata::MetadataValue; use tonic::transport::Channel; use tonic::{Request, Status}; @@ -19,7 +20,6 @@ use crate::auth::z::{self, Subject}; use crate::client::spice::spice::{ relationship_update, ObjectReference, Relationship, RelationshipUpdate, SubjectReference, }; -use crate::{ENV_SPICE_SECRET, ENV_SPICE_SERVER}; use self::spice::check_permission_response::Permissionship; use self::spice::{consistency, CheckPermissionRequest, Consistency, WriteRelationshipsRequest}; @@ -36,12 +36,7 @@ pub(crate) struct Spice { } impl Spice { - pub async fn new() -> Self { - let secret = - var(ENV_SPICE_SECRET).expect("initialization error: Failed to find SPICE_SECRET"); - let server = - var(ENV_SPICE_SERVER).expect("initialization error: Failed to find SPICE_SERVER"); - + pub async fn new(secret: String, server: String) -> Self { let channel = Channel::from_shared(server) .expect("invalid SPICE_SERVER uri") .connect() @@ -69,10 +64,10 @@ impl Spice { let response = client.check_permission(request).await?.into_inner(); - Ok(match Permissionship::from_i32(response.permissionship) { - Some(Permissionship::HasPermission) => true, - _ => false, - }) + Ok(matches!( + Permissionship::from_i32(response.permissionship), + Some(Permissionship::HasPermission) + )) } pub async fn write_relationship(&self, rs: &[z::Relationship]) -> Result<(), SpiceError> { @@ -83,7 +78,7 @@ impl Spice { let request = tonic::Request::new(WriteRelationshipsRequest { updates: rs - .into_iter() + .iter() .map(|t| RelationshipUpdate { operation: (relationship_update::Operation::Touch as i32), relationship: Some(Relationship { diff --git a/crates/secd/src/client/store/mod.rs b/crates/secd/src/client/store/mod.rs index 7bf01d5..6c42dba 100644 --- a/crates/secd/src/client/store/mod.rs +++ b/crates/secd/src/client/store/mod.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use uuid::Uuid; use crate::{ - util, Address, AddressType, AddressValidation, Credential, CredentialId, CredentialType, - Identity, IdentityId, Session, + Address, AddressType, AddressValidation, Credential, CredentialId, CredentialType, Identity, + IdentityId, }; use self::sql_db::SqlClient; @@ -60,21 +60,13 @@ pub(crate) struct IdentityLens<'a> { pub id: Option<&'a Uuid>, pub address_type: Option<&'a AddressType>, pub validated_address: Option, - pub session_token_hash: Option>, } impl<'a> Lens for IdentityLens<'a> {} -pub(crate) struct SessionLens<'a> { - pub token_hash: Option<&'a Vec>, - pub identity_id: Option<&'a IdentityId>, -} -impl<'a> Lens for SessionLens<'a> {} - pub(crate) struct CredentialLens<'a> { pub id: Option, pub identity_id: Option, pub t: Option<&'a CredentialType>, - pub restrict_by_key: Option, } impl<'a> Lens for CredentialLens<'a> {} @@ -94,7 +86,7 @@ impl<'a> Storable<'a> for Address { store: Arc, lens: &'a Self::Lens, ) -> Result, StoreError> { - let typ = lens.t.map(|at| at.to_string().clone()); + let typ = lens.t.map(|at| at.to_string()); let typ = typ.as_deref(); let val = lens.t.and_then(|at| at.get_value()); @@ -151,53 +143,17 @@ impl<'a> Storable<'a> for Identity { Ok(match store.get_type() { StoreType::Postgres { c } => { - c.find_identity( - lens.id, - val, - lens.validated_address, - &lens.session_token_hash, - ) - .await? + c.find_identity(lens.id, val, lens.validated_address) + .await? } StoreType::Sqlite { c } => { - c.find_identity( - lens.id, - val, - lens.validated_address, - &lens.session_token_hash, - ) - .await? + c.find_identity(lens.id, val, lens.validated_address) + .await? } }) } } -#[async_trait] -impl<'a> Storable<'a> for Session { - type Item = Session; - type Lens = SessionLens<'a>; - - async fn write(&self, store: Arc) -> Result<(), StoreError> { - let token_hash = util::hash(&self.token); - match store.get_type() { - StoreType::Postgres { c } => c.write_session(self, &token_hash).await?, - StoreType::Sqlite { c } => c.write_session(self, &token_hash).await?, - } - Ok(()) - } - async fn find( - store: Arc, - lens: &'a Self::Lens, - ) -> Result, StoreError> { - let token = lens.token_hash.map(|t| t.clone()).unwrap_or(vec![]); - - Ok(match store.get_type() { - StoreType::Postgres { c } => c.find_session(token, lens.identity_id).await?, - StoreType::Sqlite { c } => c.find_session(token, lens.identity_id).await?, - }) - } -} - #[async_trait] impl<'a> Storable<'a> for Credential { type Item = Credential; @@ -217,31 +173,9 @@ impl<'a> Storable<'a> for Credential { ) -> Result, StoreError> { Ok(match store.get_type() { StoreType::Postgres { c } => { - c.find_credential( - lens.id, - lens.identity_id, - lens.t, - if let Some(true) = lens.restrict_by_key { - true - } else { - false - }, - ) - .await? - } - StoreType::Sqlite { c } => { - c.find_credential( - lens.id, - lens.identity_id, - lens.t, - if let Some(true) = lens.restrict_by_key { - true - } else { - false - }, - ) - .await? + c.find_credential(lens.id, lens.identity_id, lens.t).await? } + StoreType::Sqlite { c } => c.find_credential(lens.id, lens.identity_id, lens.t).await?, }) } } diff --git a/crates/secd/src/client/store/sql_db.rs b/crates/secd/src/client/store/sql_db.rs index 3e72fe8..7b3a68e 100644 --- a/crates/secd/src/client/store/sql_db.rs +++ b/crates/secd/src/client/store/sql_db.rs @@ -1,7 +1,7 @@ use super::{Store, StoreError, StoreType}; use crate::{ - Address, AddressType, AddressValidation, AddressValidationMethod, Credential, CredentialId, - CredentialType, Identity, IdentityId, Session, + util::ErrorContext, Address, AddressType, AddressValidation, AddressValidationMethod, + Credential, CredentialId, CredentialType, Identity, IdentityId, }; use email_address::EmailAddress; use lazy_static::lazy_static; @@ -26,8 +26,6 @@ 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_SESSION: &str = "write_session"; -const FIND_SESSION: &str = "find_session"; 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"; @@ -58,14 +56,6 @@ lazy_static! { FIND_IDENTITY, include_str!("../../../store/sqlite/sql/find_identity.sql"), ), - ( - WRITE_SESSION, - include_str!("../../../store/sqlite/sql/write_session.sql"), - ), - ( - FIND_SESSION, - include_str!("../../../store/sqlite/sql/find_session.sql"), - ), ( WRITE_CREDENTIAL, include_str!("../../../store/sqlite/sql/write_credential.sql"), @@ -104,14 +94,6 @@ lazy_static! { FIND_IDENTITY, include_str!("../../../store/pg/sql/find_identity.sql"), ), - ( - WRITE_SESSION, - include_str!("../../../store/pg/sql/write_session.sql"), - ), - ( - FIND_SESSION, - include_str!("../../../store/pg/sql/find_session.sql"), - ), ( WRITE_CREDENTIAL, include_str!("../../../store/pg/sql/write_credential.sql"), @@ -145,7 +127,7 @@ impl SqlxResultExt for Result { return Err(StoreError::IdempotentCheckAlreadyExists); } } - self.map_err(|e| StoreError::SqlClientError(e)) + self.map_err(StoreError::SqlClientError) } } @@ -169,7 +151,7 @@ impl Store for PgClient { } impl PgClient { - pub async fn new(pool: sqlx::Pool) -> Arc { + pub async fn new_ref(pool: sqlx::Pool) -> Arc { sqlx::migrate!("store/pg/migrations", "secd") .run(&pool) .await @@ -196,7 +178,7 @@ impl Store for SqliteClient { } impl SqliteClient { - pub async fn new(pool: sqlx::Pool) -> Arc { + pub async fn new_ref(pool: sqlx::Pool) -> Arc { sqlx::migrate!("store/sqlite/migrations", "secd") .run(&pool) .await @@ -436,7 +418,6 @@ where id: Option<&Uuid>, address_value: Option<&str>, address_is_validated: Option, - session_token_hash: &Option>, ) -> Result, StoreError> { let sqls = get_sqls(&self.sqls_root, FIND_IDENTITY); let rs = sqlx::query_as::< @@ -452,7 +433,6 @@ where .bind(id) .bind(address_value) .bind(address_is_validated) - .bind(session_token_hash) .fetch_all(&self.pool) .await .extend_err()?; @@ -462,7 +442,8 @@ where res.push(Identity { id, address_validations: vec![], - credentials: vec![], + credentials: self.find_credential(None, Some(id), None).await?, + new_credentials: vec![], rules: vec![], metadata, created_at, @@ -473,57 +454,12 @@ where Ok(res) } - pub async fn write_session(&self, s: &Session, token_hash: &[u8]) -> Result<(), StoreError> { - let sqls = get_sqls(&self.sqls_root, WRITE_SESSION); - sqlx::query(&sqls[0]) - .bind(s.identity_id) - .bind(token_hash) - .bind(s.created_at) - .bind(s.expired_at) - .bind(s.revoked_at) - .execute(&self.pool) - .await - .extend_err()?; - - Ok(()) - } - - pub async fn find_session( - &self, - token: Vec, - identity_id: Option<&Uuid>, - ) -> Result, StoreError> { - let sqls = get_sqls(&self.sqls_root, FIND_SESSION); - let rs = - sqlx::query_as::<_, (Uuid, OffsetDateTime, OffsetDateTime, Option)>( - &sqls[0], - ) - .bind(token) - .bind(identity_id) - .bind(OffsetDateTime::now_utc()) - .bind(OffsetDateTime::now_utc()) - .fetch_all(&self.pool) - .await - .extend_err()?; - - let mut res = vec![]; - for (identity_id, created_at, expired_at, revoked_at) in rs.into_iter() { - res.push(Session { - identity_id, - token: vec![], - created_at, - expired_at, - revoked_at, - }); - } - Ok(res) - } - pub async fn write_credential(&self, c: &Credential) -> Result<(), StoreError> { let sqls = get_sqls(&self.sqls_root, WRITE_CREDENTIAL); let partial_key = match &c.t { - crate::CredentialType::Passphrase { key, value: _ } => Some(key.clone()), - _ => None, + CredentialType::Passphrase { key, .. } => Some(key.clone()), + CredentialType::ApiToken { public, .. } => Some(public.clone()), + CredentialType::Session { key, .. } => Some(key.clone()), }; sqlx::query(&sqls[0]) @@ -545,17 +481,13 @@ where id: Option, identity_id: Option, t: Option<&CredentialType>, - restrict_by_key: bool, ) -> Result, StoreError> { let sqls = get_sqls(&self.sqls_root, FIND_CREDENTIAL); - let key = restrict_by_key - .then(|| { - t.map(|i| match i { - CredentialType::Passphrase { key, value: _ } => key.clone(), - _ => todo!(), - }) - }) - .flatten(); + let key = t.map(|i| match i { + CredentialType::Passphrase { key, .. } => key.clone(), + CredentialType::ApiToken { public, .. } => public.clone(), + CredentialType::Session { key, .. } => key.clone(), + }); let rs = sqlx::query_as::< _, @@ -578,7 +510,9 @@ where let mut res = vec![]; for (id, identity_id, data, created_at, revoked_at, deleted_at) in rs.into_iter() { - let t: CredentialType = serde_json::from_str(&data)?; + let t: CredentialType = + serde_json::from_str(&data).ctx("error while deserializing credential_type")?; + res.push(Credential { id, identity_id, -- cgit v1.2.3