aboutsummaryrefslogtreecommitdiff
path: root/crates/secd/src/auth
diff options
context:
space:
mode:
Diffstat (limited to 'crates/secd/src/auth')
-rw-r--r--crates/secd/src/auth/n.rs203
-rw-r--r--crates/secd/src/auth/z/graph.rs416
-rw-r--r--crates/secd/src/auth/z/mod.rs4
3 files changed, 361 insertions, 262 deletions
diff --git a/crates/secd/src/auth/n.rs b/crates/secd/src/auth/n.rs
index 1f32fd6..dde6e7d 100644
--- a/crates/secd/src/auth/n.rs
+++ b/crates/secd/src/auth/n.rs
@@ -5,12 +5,12 @@ use crate::{
DEFAULT_SIGNUP_EMAIL,
},
store::{
- AddressLens, AddressValidationLens, CredentialLens, IdentityLens, SessionLens,
- Storable, StoreError,
+ AddressLens, AddressValidationLens, CredentialLens, IdentityLens, Storable, StoreError,
},
},
- util, Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod,
- Credential, CredentialType, Identity, IdentityId, Secd, SecdError, Session, SessionToken,
+ util::{self, ErrorContext},
+ Address, AddressType, AddressValidation, AddressValidationId, AddressValidationMethod,
+ Credential, CredentialId, CredentialType, Identity, IdentityId, Secd, SecdError,
ADDRESSS_VALIDATION_CODE_SIZE, ADDRESS_VALIDATION_ALLOWS_ATTEMPTS,
ADDRESS_VALIDATION_IDENTITY_SURJECTION, EMAIL_VALIDATION_DURATION,
};
@@ -84,8 +84,8 @@ impl Secd {
revoked_at: None,
validated_at: None,
attempts: 0,
- hashed_token: util::hash(&secret.as_bytes()),
- hashed_code: util::hash(&code.as_bytes()),
+ hashed_token: util::hash(secret.as_bytes()),
+ hashed_code: util::hash(code.as_bytes()),
};
validation.write(self.store.clone()).await?;
@@ -95,11 +95,13 @@ impl Secd {
.cfg
.email_address_from
.clone()
+ .and_then(|s| s.parse().ok())
.unwrap_or("SecD <noreply@secd.com>".parse().unwrap()),
replyto_address: self
.cfg
.email_address_replyto
.clone()
+ .and_then(|s| s.parse().ok())
.unwrap_or("SecD <noreply@secd.com>".parse().unwrap()),
recipient: email_address.clone(),
subject: "Login Request".into(),
@@ -129,7 +131,7 @@ impl Secd {
validation_id: &AddressValidationId,
plaintext_token: Option<String>,
plaintext_code: Option<String>,
- ) -> Result<Session, SecdError> {
+ ) -> Result<Credential, SecdError> {
let mut validation = AddressValidation::find(
self.store.clone(),
&AddressValidationLens {
@@ -193,7 +195,6 @@ impl Secd {
id: None,
address_type: Some(&validation.address.t),
validated_address: Some(true),
- session_token_hash: None,
},
)
.await?;
@@ -210,6 +211,7 @@ impl Secd {
id: Uuid::new_v4(),
address_validations: vec![],
credentials: vec![],
+ new_credentials: vec![],
rules: vec![],
metadata: None,
created_at: OffsetDateTime::now_utc(),
@@ -235,30 +237,72 @@ impl Secd {
validation.validated_at = Some(OffsetDateTime::now_utc());
validation.write(self.store.clone()).await?;
- let session = Session::new(validation.identity_id.expect("unreachable d3ded289-72eb-4a42-a37d-f5c9c697cc61 [assert(identity.is_some()) prevents this]"))?;
+ let mut session = Credential::new_session(validation.identity_id.expect("unreachable d3ded289-72eb-4a42-a37d-f5c9c697cc61 [assert(identity.is_some()) prevents this]"))?;
+ let plaintext_type = session.t.clone();
+
+ session.hash(&self.crypter)?;
session.write(self.store.clone()).await?;
+ session.t = plaintext_type;
+
Ok(session)
}
+ pub async fn create_identity_with_credential(
+ &self,
+ t: CredentialType,
+ identity_id: IdentityId,
+ metadata: Option<String>,
+ ) -> Result<Identity, SecdError> {
+ let identity = Identity::find(
+ self.store.clone(),
+ &IdentityLens {
+ id: Some(&identity_id),
+ address_type: None,
+ validated_address: None,
+ },
+ )
+ .await?;
+
+ if !identity.is_empty() {
+ log::error!("identity was found while creating a new identity with a credential");
+ return Err(SecdError::IdentityAlreadyExists);
+ }
+
+ Identity {
+ id: identity_id,
+ address_validations: vec![],
+ credentials: vec![],
+ new_credentials: vec![],
+ rules: vec![],
+ metadata,
+ created_at: OffsetDateTime::now_utc(),
+ deleted_at: None,
+ }
+ .write(self.store.clone())
+ .await?;
+
+ self.create_credential(t, Some(identity_id), None).await
+ }
+
pub async fn create_credential(
&self,
t: CredentialType,
identity_id: Option<IdentityId>,
+ expires_at: Option<OffsetDateTime>,
) -> Result<Identity, SecdError> {
- let identity = match identity_id {
+ let mut 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)
+ .next()
.ok_or(SecdError::IdentityNotFound)?,
None => {
@@ -266,6 +310,7 @@ impl Secd {
id: Uuid::new_v4(),
address_validations: vec![],
credentials: vec![],
+ new_credentials: vec![],
rules: vec![],
metadata: None,
created_at: OffsetDateTime::now_utc(),
@@ -282,7 +327,6 @@ impl Secd {
id: None,
identity_id: Some(identity.id),
t: Some(&t),
- restrict_by_key: Some(false),
},
)
.await?[..]
@@ -290,9 +334,9 @@ impl Secd {
[] => Credential {
id: Uuid::new_v4(),
identity_id: identity.id,
- t,
+ t: t.clone(),
created_at: OffsetDateTime::now_utc(),
- revoked_at: None,
+ revoked_at: expires_at,
deleted_at: None,
},
_ => return Err(SecdError::CredentialAlreadyExists),
@@ -307,57 +351,100 @@ impl Secd {
err => SecdError::StoreError(err),
})?;
- Ok(identity)
- }
+ identity.new_credentials.push(Credential {
+ id: credential.id,
+ identity_id: credential.identity_id,
+ t,
+ created_at: credential.created_at,
+ revoked_at: credential.revoked_at,
+ deleted_at: credential.deleted_at,
+ });
- pub async fn validate_credential(
- &self,
- // t: CredentialType,
- // key: String,
- // value: Option<String>,
- ) -> Result<Session, SecdError> {
- // Credential::find(store, lens) use key here as unique index
- todo!()
+ Ok(identity)
}
- pub async fn get_session(&self, t: &SessionToken) -> Result<Session, SecdError> {
- let token = hex::decode(t)?;
- let mut session = Session::find(
+ pub async fn validate_credential(&self, t: CredentialType) -> Result<Credential, SecdError> {
+ let mut retrieved = Credential::find(
self.store.clone(),
- &SessionLens {
- token_hash: Some(&util::hash(&token)),
+ &CredentialLens {
+ id: None,
identity_id: None,
+ t: Some(&t),
},
)
- .await?;
- assert!(session.len() <= 1, "get session failed: multiple sessions found for a single token. This is very _very_ bad.");
+ .await?
+ .into_iter()
+ .next()
+ .ok_or(SecdError::InvalidCredential)?;
- if session.is_empty() {
- return Err(SecdError::InvalidSession);
- } else {
- let mut session = session.swap_remove(0);
- session.token = token;
- Ok(session)
- }
+ match retrieved.revoked_at {
+ Some(t) if t <= OffsetDateTime::now_utc() => {
+ log::debug!("credential was revoked");
+ Err(SecdError::InvalidCredential)
+ }
+ _ => Ok(()),
+ }?;
+
+ match retrieved.deleted_at {
+ Some(t) if t <= OffsetDateTime::now_utc() => {
+ log::debug!("credential was deleted");
+ Err(SecdError::InvalidCredential)
+ }
+ _ => Ok(()),
+ }?;
+
+ retrieved.hash_compare(&t, &self.crypter)?;
+
+ // Return the initially provided plaintext credential since it's valid
+ retrieved.t = t;
+
+ Ok(retrieved)
}
pub async fn get_identity(
&self,
i: Option<IdentityId>,
- t: Option<SessionToken>,
+ t: Option<CredentialType>,
) -> Result<Identity, SecdError> {
- let token_hash = match t {
- Some(tok) => Some(util::hash(&hex::decode(&tok)?)),
- None => None,
- };
+ if i.is_none() && t.is_none() {
+ log::error!("get_identity expects that at least one of IdentityId or CredentialType is provided. None were found.");
+ return Err(SecdError::IdentityNotFound);
+ }
+
+ let c = Credential::find(
+ self.store.clone(),
+ &CredentialLens {
+ id: None,
+ identity_id: i,
+ t: t.as_ref(),
+ },
+ )
+ .await?;
+
+ assert!(
+ c.len() <= 1,
+ "The provided credential refers to more than one identity. This is very _very_ bad."
+ );
+ let identity_id = c
+ .into_iter()
+ .next()
+ .ok_or(SecdError::InvalidCredential)
+ .ctx("No identities were found for the provided identity_id and credential_type")?
+ .identity_id;
+
+ if i.is_some() && i != Some(identity_id) {
+ log::error!(
+ "The provided identity does not match the identity associated with this credential"
+ );
+ return Err(SecdError::InvalidCredential);
+ }
let mut i = Identity::find(
self.store.clone(),
&IdentityLens {
- id: i.as_ref(),
+ id: Some(&identity_id),
address_type: None,
validated_address: None,
- session_token_hash: token_hash,
},
)
.await?;
@@ -368,7 +455,7 @@ impl Secd {
);
if i.is_empty() {
- return Err(SecdError::IdentityNotFound);
+ Err(SecdError::IdentityNotFound)
} else {
Ok(i.swap_remove(0))
}
@@ -385,12 +472,11 @@ impl Secd {
id: Some(&i),
address_type: None,
validated_address: None,
- session_token_hash: None,
},
)
.await?
.into_iter()
- .nth(0)
+ .next()
.ok_or(SecdError::IdentityNotFound)?;
identity.metadata = Some(md);
@@ -399,9 +485,22 @@ impl Secd {
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?;
+ pub async fn revoke_credential(&self, credential_id: CredentialId) -> Result<(), SecdError> {
+ let mut credential = Credential::find(
+ self.store.clone(),
+ &CredentialLens {
+ id: Some(credential_id),
+ identity_id: None,
+ t: None,
+ },
+ )
+ .await?
+ .into_iter()
+ .next()
+ .ok_or(SecdError::InvalidCredential)?;
+
+ credential.revoked_at = Some(OffsetDateTime::now_utc());
+ credential.write(self.store.clone()).await?;
Ok(())
}
}
diff --git a/crates/secd/src/auth/z/graph.rs b/crates/secd/src/auth/z/graph.rs
index 9ca045b..f28c901 100644
--- a/crates/secd/src/auth/z/graph.rs
+++ b/crates/secd/src/auth/z/graph.rs
@@ -1,209 +1,207 @@
-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);
- }
-}
+// 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
+
+// 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/mod.rs b/crates/secd/src/auth/z/mod.rs
index b364583..d663e65 100644
--- a/crates/secd/src/auth/z/mod.rs
+++ b/crates/secd/src/auth/z/mod.rs
@@ -1,3 +1,5 @@
+#![allow(dead_code)] // TODO: Remove when implemented
+#![allow(unused_variables)]
mod graph;
use crate::{Authorization, Secd, SecdError};
@@ -50,7 +52,7 @@ impl Authorization for Secd {
// they are "relationships" rather than what spice calls permissions
spice
.write_relationship(
- &ts.into_iter()
+ &ts.iter()
.map(|r| Relationship {
subject: r.subject.clone(),
object: r.object.clone(),