diff options
| author | benj <benj@rse8.com> | 2023-05-22 15:47:06 -0700 |
|---|---|---|
| committer | benj <benj@rse8.com> | 2023-05-22 15:47:06 -0700 |
| commit | ed34a5251f13bbded0aa15719887db4924b351eb (patch) | |
| tree | 9719d805e915f4483d5db3e5e612e8b4cf5c702c /crates/secd/src/auth | |
| parent | eb92f823c31a5e702af7005231f0d6915aad3342 (diff) | |
| download | secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.gz secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.bz2 secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.lz secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.xz secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.tar.zst secdiam-ed34a5251f13bbded0aa15719887db4924b351eb.zip | |
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.
Diffstat (limited to '')
| -rw-r--r-- | crates/secd/src/auth/n.rs | 203 | ||||
| -rw-r--r-- | crates/secd/src/auth/z/graph.rs | 416 | ||||
| -rw-r--r-- | crates/secd/src/auth/z/mod.rs | 4 |
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(), |
