aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/auth
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/secd/src/auth/n.rs166
-rw-r--r--crates/secd/src/auth/z/graph.rs209
-rw-r--r--crates/secd/src/auth/z/mod.rs (renamed from crates/secd/src/auth/z.rs)43
3 files changed, 387 insertions, 31 deletions
diff --git a/crates/secd/src/auth/n.rs b/crates/secd/src/auth/n.rs
index 1d3b2d5..1f32fd6 100644
--- a/crates/secd/src/auth/n.rs
+++ b/crates/secd/src/auth/n.rs
@@ -1,20 +1,23 @@
-use std::str::FromStr;
-
use crate::{
client::{
- email::{EmailValidationMessage, Sendable},
+ email::{
+ parse_email_template, EmailValidationMessage, Sendable, DEFAULT_SIGNIN_EMAIL,
+ DEFAULT_SIGNUP_EMAIL,
+ },
store::{
- AddressLens, AddressValidationLens, IdentityLens, SessionLens, Storable, StoreError,
+ AddressLens, AddressValidationLens, CredentialLens, IdentityLens, SessionLens,
+ Storable, StoreError,
},
},
util, Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod,
- Credential, CredentialType, Identity, PhoneNumber, Secd, SecdError, Session, SessionToken,
+ Credential, CredentialType, Identity, IdentityId, Secd, SecdError, Session, SessionToken,
ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS,
ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION,
};
use email_address::EmailAddress;
use log::warn;
use rand::Rng;
+use std::str::FromStr;
use time::{Duration, OffsetDateTime};
use uuid::Uuid;
@@ -22,10 +25,15 @@ impl Secd {
pub async fn validate_email(
&self,
email_address: &str,
- identity_id: Option<Uuid>,
+ identity_id: Option<IdentityId>,
) -> Result<AddressValidation, SecdError> {
let email_address = EmailAddress::from_str(email_address)?;
- // record address (idempotent operation)
+ let mut email_template = self
+ .cfg
+ .email_signup_message
+ .clone()
+ .unwrap_or(DEFAULT_SIGNUP_EMAIL.into());
+
let mut address = Address {
id: Uuid::new_v4(),
t: AddressType::Email {
@@ -50,6 +58,12 @@ impl Secd {
.into_iter()
.next()
.ok_or(SecdError::AddressValidationFailed)?;
+
+ email_template = self
+ .cfg
+ .email_signin_message
+ .clone()
+ .unwrap_or(DEFAULT_SIGNIN_EMAIL.into());
}
let secret = hex::encode(rand::thread_rng().gen::<[u8; 32]>());
@@ -76,13 +90,23 @@ impl Secd {
validation.write(self.store.clone()).await?;
- let msg =EmailValidationMessage {
+ let msg = EmailValidationMessage {
+ from_address: self
+ .cfg
+ .email_address_from
+ .clone()
+ .unwrap_or("SecD <noreply@secd.com>".parse().unwrap()),
+ replyto_address: self
+ .cfg
+ .email_address_replyto
+ .clone()
+ .unwrap_or("SecD <noreply@secd.com>".parse().unwrap()),
recipient: email_address.clone(),
- subject: "Confirm Your Email".into(),
- body: format!("This is an email validation message. Click this link [{:?}?s={}] or use the code [{}]", validation.id, secret, code),
+ subject: "Login Request".into(),
+ body: parse_email_template(&email_template, validation.id, Some(secret), Some(code))?,
};
- match msg.send().await {
+ match msg.send(self.email_messenger.clone()).await {
Ok(_) => { /* TODO: Write down the message*/ }
Err(e) => {
validation.revoked_at = Some(OffsetDateTime::now_utc());
@@ -95,10 +119,11 @@ impl Secd {
}
pub async fn validate_sms(
&self,
- phone_number: &PhoneNumber,
+ // phone_number: &PhoneNumber,
) -> Result<AddressValidation, SecdError> {
todo!()
}
+
pub async fn complete_address_validation(
&self,
validation_id: &AddressValidationId,
@@ -215,21 +240,83 @@ impl Secd {
Ok(session)
}
+
pub async fn create_credential(
&self,
t: CredentialType,
- key: String,
- value: Option<String>,
- ) -> Result<Credential, SecdError> {
- todo!()
+ identity_id: Option<IdentityId>,
+ ) -> Result<Identity, SecdError> {
+ let identity = match identity_id {
+ Some(id) => Identity::find(
+ self.store.clone(),
+ &IdentityLens {
+ id: Some(&id),
+ address_type: None,
+ validated_address: None,
+ session_token_hash: None,
+ },
+ )
+ .await?
+ .into_iter()
+ .nth(0)
+ .ok_or(SecdError::IdentityNotFound)?,
+
+ None => {
+ let id = Identity {
+ id: Uuid::new_v4(),
+ address_validations: vec![],
+ credentials: vec![],
+ rules: vec![],
+ metadata: None,
+ created_at: OffsetDateTime::now_utc(),
+ deleted_at: None,
+ };
+ id.write(self.store.clone()).await?;
+ id
+ }
+ };
+
+ let mut credential = match &Credential::find(
+ self.store.clone(),
+ &CredentialLens {
+ id: None,
+ identity_id: Some(identity.id),
+ t: Some(&t),
+ restrict_by_key: Some(false),
+ },
+ )
+ .await?[..]
+ {
+ [] => Credential {
+ id: Uuid::new_v4(),
+ identity_id: identity.id,
+ t,
+ created_at: OffsetDateTime::now_utc(),
+ revoked_at: None,
+ deleted_at: None,
+ },
+ _ => return Err(SecdError::CredentialAlreadyExists),
+ };
+
+ credential.hash(&self.crypter)?;
+ credential
+ .write(self.store.clone())
+ .await
+ .map_err(|err| match err {
+ StoreError::IdempotentCheckAlreadyExists => SecdError::CredentialAlreadyExists,
+ err => SecdError::StoreError(err),
+ })?;
+
+ Ok(identity)
}
pub async fn validate_credential(
&self,
- t: CredentialType,
- key: String,
- value: Option<String>,
+ // t: CredentialType,
+ // key: String,
+ // value: Option<String>,
) -> Result<Session, SecdError> {
+ // Credential::find(store, lens) use key here as unique index
todo!()
}
@@ -254,15 +341,23 @@ impl Secd {
}
}
- pub async fn get_identity(&self, i: &SessionToken) -> Result<Identity, SecdError> {
- let token_hash = util::hash(&hex::decode(i)?);
+ pub async fn get_identity(
+ &self,
+ i: Option<IdentityId>,
+ t: Option<SessionToken>,
+ ) -> Result<Identity, SecdError> {
+ let token_hash = match t {
+ Some(tok) => Some(util::hash(&hex::decode(&tok)?)),
+ None => None,
+ };
+
let mut i = Identity::find(
self.store.clone(),
&IdentityLens {
- id: None,
+ id: i.as_ref(),
address_type: None,
validated_address: None,
- session_token_hash: Some(token_hash),
+ session_token_hash: token_hash,
},
)
.await?;
@@ -279,6 +374,31 @@ impl Secd {
}
}
+ pub async fn update_identity_metadata(
+ &self,
+ i: IdentityId,
+ md: String,
+ ) -> Result<Identity, SecdError> {
+ let mut identity = Identity::find(
+ self.store.clone(),
+ &IdentityLens {
+ id: Some(&i),
+ address_type: None,
+ validated_address: None,
+ session_token_hash: None,
+ },
+ )
+ .await?
+ .into_iter()
+ .nth(0)
+ .ok_or(SecdError::IdentityNotFound)?;
+
+ identity.metadata = Some(md);
+ identity.write(self.store.clone()).await?;
+
+ Ok(identity)
+ }
+
pub async fn revoke_session(&self, session: &mut Session) -> Result<(), SecdError> {
session.revoked_at = Some(OffsetDateTime::now_utc());
session.write(self.store.clone()).await?;
diff --git a/crates/secd/src/auth/z/graph.rs b/crates/secd/src/auth/z/graph.rs
new file mode 100644
index 0000000..9ca045b
--- /dev/null
+++ b/crates/secd/src/auth/z/graph.rs
@@ -0,0 +1,209 @@
+use std::collections::{HashSet, VecDeque};
+
+type NodeIdx = usize;
+type EdgeIdx = usize;
+
+struct RelationGraph {
+ nodes: Vec<Node>,
+ edges: Vec<Edge>,
+}
+
+#[derive(Hash, PartialEq, Eq)]
+struct Node {
+ id: u64,
+ is_namespace: bool,
+ edge_list_head: Option<EdgeIdx>,
+}
+
+struct Edge {
+ dst: NodeIdx,
+ op: Option<Operator>,
+ next_edge: Option<EdgeIdx>,
+}
+
+enum Operator {
+ And,
+ Or,
+ Difference,
+ Complement,
+ AndAll,
+ OrAll,
+ DifferenceAll,
+ ComplementAll,
+}
+
+impl RelationGraph {
+ pub fn new() -> Self {
+ // As I think about how to serialize this graph, when reading from the DB
+ // I should chunk up the namespaces so that I can read-on-demand when I
+ // encounter a namespace that has not yet been loaded, and in this way
+ // I don't need to deal with the entire graph...
+ //
+ // but that's a totally unnecessary fun optimization.
+ RelationGraph {
+ nodes: vec![],
+ edges: vec![],
+ }
+ }
+
+ pub fn add_node(&mut self, id: u64, is_namespace: bool) -> NodeIdx {
+ let idx = self.nodes.len();
+ self.nodes.push(Node {
+ id,
+ is_namespace,
+ edge_list_head: None,
+ });
+ idx
+ }
+
+ pub fn add_edge(
+ &mut self,
+ src: NodeIdx,
+ dst: NodeIdx,
+ op: Option<Operator>,
+ follow: Option<NodeIdx>,
+ ) {
+ let edge_idx = self.edges.len();
+ let node = &mut self.nodes[src];
+ // TODO: dupe check
+ self.edges.push(Edge {
+ dst,
+ op,
+ next_edge: node.edge_list_head,
+ });
+ node.edge_list_head = Some(edge_idx);
+ }
+
+ // doc:2#viewer@user:1
+ pub fn find_leaves(&self, src: NodeIdx, filterIdx: Option<NodeIdx>) -> Vec<NodeIdx> {
+ let mut all_leaves = vec![];
+ // let mut and_leaves = vec![];
+ // let mut exc_leaves = vec![];
+
+ // the output should basically be leaves to look up
+ // for example
+ // the leaves of doc might be (user), <(organization, admin)>, <(role, foo)>
+ // and since (organization, admin) has leaves (user) as well
+ // if the user is in that final set, then they are valid
+ // and those leaves would be queried as relation tuples saved in the DB
+
+ println!("HERE I AM");
+
+ let start = self.nodes.get(src);
+ let mut seen = HashSet::new();
+ let mut q = VecDeque::new();
+
+ seen.insert(start);
+ q.push_back(start);
+
+ // I need to build a chain of ops
+ // [AND, OR, OR, AND, OR, ...]
+ // at each stage, I need to apply the operator chain
+ while let Some(&Some(node)) = q.iter().next() {
+ if node.is_namespace {
+ all_leaves.push(node);
+ }
+
+ // for edge in edge list
+
+ println!("IN QUEUE\n");
+ println!("node: {:?}\n", node.id);
+ q.pop_front();
+ }
+ println!("DONE");
+
+ // task...starting at doc_viewer, return all leaf nodes
+ // to do this....
+ // check all children
+ // if child is OR
+ // if child is leaf, return child
+ // else return all of these children
+ // if child is AND
+ // if child is leaf, return if also present in all other nodes
+ // else return all children which are also present in other nodes
+ // if child is Difference
+ // if child is leaf, do not include and remove if it part of the user set
+ // else remove all of these children
+
+ // TOTAL pool of leafs
+ // Intersection leaves (i.e. ONLY leaves which MUST exist)
+ // exclusion leaves (i.e. leaves which CANNOT exist)
+
+ // bfs build query...
+ // execute query
+
+ // start at nodeIdx: doc_viewer
+ // expand until I find all leaf nodes.
+ // we are interested in leaf nodes with nodeIdx: user
+ // return all leaf nodeIdx that match the filter. If no filter provided, return all leaf idx.
+
+ // TODO: optionally build the expansion path by pushing every intermediate step as the bfs is walked.
+
+ // with each step of the bfs, perform the operator.
+ // e.g. if first step is doc_viewer -> user with operator OR then we have leaf user with operator chain [OR]
+ // the next step might be doc_viewer -> doc_editor with step OR and doc_editor -> user with step OR, so we have leaf user with chain [OR, OR]
+ // the next step might be doc_viewer -> doc_auditor with OR
+ // then doc_auditor -> doc_editor with OR
+ // then doc_editor -> doc_user with OR
+ // then doc_editor -> doc_owner
+ // then doc_auditor -> doc_owner with Difference
+ //
+
+ todo!()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn build_graph_test() {
+ let mut g = RelationGraph::new();
+ let user = g.add_node(1, true);
+ let role = g.add_node(2, true);
+ let group = g.add_node(3, true);
+ let doc = g.add_node(4, true);
+
+ let role_member = g.add_node(5, false);
+
+ let group_member = g.add_node(6, false);
+ let group_admin = g.add_node(7, false);
+
+ let doc_owner = g.add_node(8, false);
+ let doc_editor = g.add_node(9, false);
+ let doc_viewer = g.add_node(10, false);
+ let doc_auditor = g.add_node(11, false);
+ let doc_parent = g.add_node(12, false);
+
+ g.add_edge(role, role_member, None, None);
+ g.add_edge(role_member, user, Some(Operator::Or), None);
+ g.add_edge(role_member, group_member, Some(Operator::Or), None);
+
+ g.add_edge(group, group_member, None, None);
+ g.add_edge(group, group_admin, None, None);
+
+ g.add_edge(group_member, user, Some(Operator::Or), None);
+ g.add_edge(group_member, group_admin, Some(Operator::Or), None);
+ g.add_edge(group_admin, user, Some(Operator::Or), None);
+
+ g.add_edge(doc, doc_owner, None, None);
+ g.add_edge(doc, doc_editor, None, None);
+ g.add_edge(doc, doc_viewer, None, None);
+ g.add_edge(doc, doc_auditor, None, None);
+ g.add_edge(doc, doc_parent, None, None);
+
+ g.add_edge(doc_owner, user, Some(Operator::Or), None);
+ g.add_edge(doc_editor, user, Some(Operator::Or), None);
+ g.add_edge(doc_editor, doc_owner, Some(Operator::Or), None);
+ g.add_edge(doc_viewer, user, Some(Operator::Or), None);
+ g.add_edge(doc_viewer, doc_editor, Some(Operator::Or), None);
+ g.add_edge(doc_viewer, doc_parent, Some(Operator::Or), Some(doc_viewer));
+ g.add_edge(doc_viewer, user, Some(Operator::OrAll), None);
+ g.add_edge(doc_viewer, doc_auditor, Some(Operator::Or), None);
+ g.add_edge(doc_auditor, doc_editor, Some(Operator::Difference), None);
+
+ g.find_leaves(doc_viewer, None);
+ assert_eq!(1, 2);
+ }
+}
diff --git a/crates/secd/src/auth/z.rs b/crates/secd/src/auth/z/mod.rs
index 31f449c..b364583 100644
--- a/crates/secd/src/auth/z.rs
+++ b/crates/secd/src/auth/z/mod.rs
@@ -1,6 +1,8 @@
-use uuid::Uuid;
+mod graph;
-use crate::{Secd, SecdError};
+use crate::{Authorization, Secd, SecdError};
+use async_trait::async_trait;
+use uuid::Uuid;
pub type Namespace = String;
pub type Object = (Namespace, Uuid);
@@ -18,8 +20,9 @@ pub enum Subject {
UserSet { user: Object, relation: Relation },
}
-impl Secd {
- pub async fn check(&self, r: &Relationship) -> Result<bool, SecdError> {
+#[async_trait]
+impl Authorization for Secd {
+ async fn check(&self, r: &Relationship) -> Result<bool, SecdError> {
let spice = self
.spice
.clone()
@@ -27,16 +30,16 @@ impl Secd {
Ok(spice.check_permission(r).await?)
}
- pub async fn expand(&self) -> Result<(), SecdError> {
+ async fn expand(&self) -> Result<(), SecdError> {
todo!()
}
- pub async fn read(&self) -> Result<(), SecdError> {
+ async fn read(&self) -> Result<(), SecdError> {
todo!()
}
- pub async fn watch(&self) -> Result<(), SecdError> {
+ async fn watch(&self) -> Result<(), SecdError> {
unimplemented!()
}
- pub async fn write(&self, ts: &[Relationship]) -> Result<(), SecdError> {
+ async fn write(&self, ts: &[Relationship]) -> Result<(), SecdError> {
let spice = self
.spice
.clone()
@@ -59,3 +62,27 @@ impl Secd {
Ok(())
}
}
+
+enum RelationToken {
+ Start,
+ Or,
+ And,
+ Exclude,
+}
+struct RelationContainer {
+ name: Relation,
+ bins: Vec<(RelationToken, Relation)>,
+}
+
+struct NamespaceContainer {
+ relations: Vec<RelationContainer>,
+}
+
+impl Secd {
+ async fn write_namespace(&self, ns: &NamespaceContainer) -> Result<(), SecdError> {
+ todo!()
+ }
+ async fn read_namespace(&self) -> Result<NamespaceContainer, SecdError> {
+ todo!()
+ }
+}