mirror of
https://github.com/pfzetto/rebacs
synced 2024-11-22 19:22:51 +01:00
major rework
This commit is contained in:
parent
92090ffc3d
commit
a39abdacb0
6 changed files with 596 additions and 268 deletions
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::{Borrow, Cow},
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::{BTreeSet, HashSet},
|
collections::{BTreeSet, HashSet},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
|
@ -15,87 +15,183 @@ use tokio::{
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
const WILDCARD_ID: &str = "*";
|
||||||
pub struct NodeId {
|
const WRITE_RELATION: &str = "grant";
|
||||||
pub namespace: String,
|
|
||||||
pub id: String,
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||||
pub relation: Option<String>,
|
pub struct VertexId {
|
||||||
|
namespace: String,
|
||||||
|
id: String,
|
||||||
|
relation: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||||
|
pub struct VertexIdRef<'a> {
|
||||||
|
namespace: &'a str,
|
||||||
|
id: &'a str,
|
||||||
|
relation: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Node {
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub id: NodeId,
|
pub enum RObjectOrSet<'a> {
|
||||||
pub edges_in: RwLock<Vec<Arc<Node>>>,
|
Object(Cow<'a, RObject>),
|
||||||
pub edges_out: RwLock<Vec<Arc<Node>>>,
|
Set(Cow<'a, RSet>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RSet(VertexId);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RObject(VertexId);
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
id: VertexId,
|
||||||
|
edges_in: RwLock<HashSet<Arc<Vertex>>>,
|
||||||
|
edges_out: RwLock<HashSet<Arc<Vertex>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RelationGraph {
|
pub struct RelationGraph {
|
||||||
nodes: RwLock<BTreeSet<Arc<Node>>>,
|
verticies: RwLock<BTreeSet<Arc<Vertex>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait VertexIdentifier {
|
||||||
|
fn namespace(&self) -> &str;
|
||||||
|
fn id(&self) -> &str;
|
||||||
|
fn relation(&self) -> Option<&str>;
|
||||||
|
fn vertex_id(&self) -> &VertexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RObject {
|
||||||
|
pub fn namespace(&self) -> &str {
|
||||||
|
&self.0.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
&self.0.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relation(&self) -> Option<&str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn vertex_id(&self) -> &VertexId {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RSet {
|
||||||
|
pub fn namespace(&self) -> &str {
|
||||||
|
&self.0.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
&self.0.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relation(&self) -> &str {
|
||||||
|
self.0.relation.as_deref().unwrap_or("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex_id(&self) -> &VertexId {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RObjectOrSet<'a> {
|
||||||
|
pub fn namespace(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Object(obj) => obj.namespace(),
|
||||||
|
Self::Set(set) => set.namespace(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Object(obj) => obj.id(),
|
||||||
|
Self::Set(set) => set.id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relation(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Object(_) => None,
|
||||||
|
Self::Set(set) => set.0.relation.as_deref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex_id(&self) -> &VertexId {
|
||||||
|
match self {
|
||||||
|
Self::Object(obj) => obj.vertex_id(),
|
||||||
|
Self::Set(set) => set.vertex_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelationGraph {
|
impl RelationGraph {
|
||||||
pub async fn insert(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) {
|
pub async fn insert(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) {
|
||||||
let src = src.into();
|
let src: RObjectOrSet<'_> = src.into();
|
||||||
let dst = dst.into();
|
let mut verticies = self.verticies.write().await;
|
||||||
|
|
||||||
let mut nodes = self.nodes.write().await;
|
let mut get_or_create = |vertex: &VertexId| match verticies.get(vertex) {
|
||||||
|
Some(vertex) => vertex.clone(),
|
||||||
let src_node = match nodes.get(&src) {
|
|
||||||
Some(node) => node.clone(),
|
|
||||||
None => {
|
None => {
|
||||||
let node = Arc::new(Node {
|
let vertex = Arc::new(Vertex {
|
||||||
id: src,
|
id: vertex.clone(),
|
||||||
edges_out: RwLock::new(vec![]),
|
edges_out: RwLock::new(HashSet::new()),
|
||||||
edges_in: RwLock::new(vec![]),
|
edges_in: RwLock::new(HashSet::new()),
|
||||||
});
|
});
|
||||||
nodes.insert(node.clone());
|
verticies.insert(vertex.clone());
|
||||||
node
|
vertex
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let dst_node = match nodes.get(&dst).cloned() {
|
|
||||||
Some(node) => node.clone(),
|
let src_without_relation = src.relation().is_none();
|
||||||
None => {
|
|
||||||
let node = Arc::new(Node {
|
let src_wildcard: RObjectOrSet = (src.namespace(), WILDCARD_ID, src.relation()).into();
|
||||||
id: dst,
|
let src_wildcard = get_or_create(src_wildcard.vertex_id());
|
||||||
edges_out: RwLock::new(vec![]),
|
let src_vertex = get_or_create(src.vertex_id());
|
||||||
edges_in: RwLock::new(vec![]),
|
|
||||||
});
|
let dst_wildcard: RSet = (dst.namespace(), WILDCARD_ID, dst.relation()).into();
|
||||||
nodes.insert(node.clone());
|
let dst_wildcard = get_or_create(dst_wildcard.vertex_id());
|
||||||
node
|
let dst_vertex = get_or_create(dst.vertex_id());
|
||||||
}
|
|
||||||
};
|
if src_without_relation && src_vertex.id.id != WILDCARD_ID {
|
||||||
add_edge(src_node, dst_node).await;
|
add_edge(src_vertex.clone(), src_wildcard).await;
|
||||||
|
} else if !src_without_relation {
|
||||||
|
add_edge(src_wildcard, src_vertex.clone()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_edge(dst_wildcard, dst_vertex.clone()).await;
|
||||||
|
add_edge(src_vertex, dst_vertex).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) {
|
pub async fn remove(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) {
|
||||||
let src = src.into();
|
let src: RObjectOrSet<'_> = src.into();
|
||||||
let dst = dst.into();
|
let mut verticies = self.verticies.write().await;
|
||||||
|
|
||||||
let mut nodes = self.nodes.write().await;
|
let src = verticies.get(src.vertex_id()).cloned();
|
||||||
|
let dst = verticies.get(dst.vertex_id()).cloned();
|
||||||
let src = nodes.get(&src).cloned();
|
|
||||||
let dst = nodes.get(&dst).cloned();
|
|
||||||
|
|
||||||
if let (Some(src), Some(dst)) = (src, dst) {
|
if let (Some(src), Some(dst)) = (src, dst) {
|
||||||
src.edges_out.write().await.retain(|x| x != &dst);
|
src.edges_out.write().await.retain(|x| x != &dst);
|
||||||
dst.edges_in.write().await.retain(|x| x != &src);
|
dst.edges_in.write().await.retain(|x| x != &src);
|
||||||
|
|
||||||
if src.edges_in.read().await.is_empty() && src.edges_out.read().await.is_empty() {
|
if src.edges_in.read().await.is_empty() && src.edges_out.read().await.is_empty() {
|
||||||
nodes.remove(&src.id);
|
verticies.remove(&src.id);
|
||||||
}
|
}
|
||||||
if dst.edges_in.read().await.is_empty() && dst.edges_out.read().await.is_empty() {
|
if dst.edges_in.read().await.is_empty() && dst.edges_out.read().await.is_empty() {
|
||||||
nodes.remove(&dst.id);
|
verticies.remove(&dst.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn has(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) -> bool {
|
/// does a edge from src to dst exist
|
||||||
let src = src.into();
|
pub async fn has(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) -> bool {
|
||||||
let dst = dst.into();
|
let src: RObjectOrSet<'_> = src.into();
|
||||||
|
|
||||||
let (src, dst) = {
|
let (src, dst) = {
|
||||||
let nodes = self.nodes.read().await;
|
let verticies = self.verticies.read().await;
|
||||||
(nodes.get(&src).cloned(), nodes.get(&dst).cloned())
|
(
|
||||||
|
verticies.get(src.vertex_id()).cloned(),
|
||||||
|
verticies.get(dst.vertex_id()).cloned(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let (Some(src), Some(dst)) = (src, dst) {
|
if let (Some(src), Some(dst)) = (src, dst) {
|
||||||
|
@ -106,50 +202,63 @@ impl RelationGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// checks if there is a path between src and dst using BFS
|
/// checks if there is a path between src and dst using BFS
|
||||||
pub async fn has_recursive<'a>(
|
pub async fn check<'a>(
|
||||||
&self,
|
&self,
|
||||||
src: impl Into<NodeId>,
|
src: impl Into<RObjectOrSet<'_>>,
|
||||||
dst: impl Into<NodeId>,
|
dst: &RSet,
|
||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let src = src.into();
|
let src: RObjectOrSet<'_> = src.into();
|
||||||
let dst = dst.into();
|
|
||||||
|
|
||||||
let src = if let Some(src) = self.nodes.read().await.get(&src) {
|
|
||||||
src.clone()
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut distance = 1;
|
let mut distance = 1;
|
||||||
|
|
||||||
let mut neighbors = src
|
let mut neighbors: Vec<Arc<Vertex>> = if let Some(src) =
|
||||||
.edges_out
|
self.verticies.read().await.get(src.vertex_id())
|
||||||
.read()
|
{
|
||||||
.await
|
src.edges_out.read().await.iter().cloned().collect()
|
||||||
.iter()
|
} else {
|
||||||
.cloned()
|
let wildcard_src: RObject = (src.namespace(), WILDCARD_ID).into();
|
||||||
.collect::<Vec<_>>();
|
if let Some(wildcard_src) = self.verticies.read().await.get(wildcard_src.vertex_id()) {
|
||||||
|
wildcard_src
|
||||||
|
.edges_out
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut visited: HashSet<Arc<Node>> = HashSet::new();
|
let mut visited: HashSet<Arc<Vertex>> = HashSet::new();
|
||||||
|
|
||||||
while !neighbors.is_empty() {
|
while !neighbors.is_empty() {
|
||||||
|
if let Some(limit) = limit {
|
||||||
|
if distance > limit {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut next_neighbors = vec![];
|
let mut next_neighbors = vec![];
|
||||||
for neighbor in neighbors {
|
for neighbor in neighbors {
|
||||||
if distance > 1 && visited.contains(&neighbor) {
|
if distance > 1 && visited.contains(&neighbor) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if neighbor.id == dst {
|
|
||||||
|
//check if the current vertex is the dst vertex or the wildcard vertex for the dst
|
||||||
|
//namespace. Without checking the wildcard vertex, not initialized dsts that should
|
||||||
|
//be affected by the wildcard wouldn't be found.
|
||||||
|
if &neighbor.id == dst
|
||||||
|
|| (neighbor.id.namespace == dst.namespace()
|
||||||
|
&& neighbor.id.id == WILDCARD_ID
|
||||||
|
&& neighbor.id.relation.as_deref() == Some(dst.relation()))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if let Some(limit) = limit {
|
|
||||||
if distance > limit {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut node_neighbors = neighbor.edges_out.read().await.clone();
|
let mut vertex_neighbors =
|
||||||
next_neighbors.append(&mut node_neighbors);
|
neighbor.edges_out.read().await.iter().cloned().collect();
|
||||||
|
next_neighbors.append(&mut vertex_neighbors);
|
||||||
|
|
||||||
visited.insert(neighbor);
|
visited.insert(neighbor);
|
||||||
}
|
}
|
||||||
|
@ -159,11 +268,93 @@ impl RelationGraph {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn can_write(
|
||||||
|
&self,
|
||||||
|
src: impl Into<RObjectOrSet<'_>>,
|
||||||
|
dst: &RSet,
|
||||||
|
limit: Option<u32>,
|
||||||
|
) -> bool {
|
||||||
|
let mut dst = dst.clone();
|
||||||
|
|
||||||
|
dst.0.relation = Some(WRITE_RELATION.to_string());
|
||||||
|
self.check(src, &dst, limit).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn expand(&self, dst: &RSet) -> Vec<(RObject, Vec<RSet>)> {
|
||||||
|
let start_vertex = {
|
||||||
|
let verticies = self.verticies.read().await;
|
||||||
|
match verticies.get(dst.vertex_id()) {
|
||||||
|
Some(v) => v.clone(),
|
||||||
|
None => {
|
||||||
|
let wildcard_dst: RSet = (dst.namespace(), WILDCARD_ID, dst.relation()).into();
|
||||||
|
|
||||||
|
match verticies.get(wildcard_dst.vertex_id()) {
|
||||||
|
Some(v) => v.clone(),
|
||||||
|
None => return vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut visited: HashSet<Arc<Vertex>> = HashSet::new();
|
||||||
|
|
||||||
|
let mut neighbors: Vec<(Arc<Vertex>, Vec<Arc<Vertex>>)> = start_vertex
|
||||||
|
.edges_in
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.map(|v| (v.clone(), vec![start_vertex.clone()]))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
visited.insert(start_vertex);
|
||||||
|
|
||||||
|
let mut expanded_verticies: Vec<(Arc<Vertex>, Vec<Arc<Vertex>>)> = vec![];
|
||||||
|
|
||||||
|
while !neighbors.is_empty() {
|
||||||
|
let mut next_neighbors = vec![];
|
||||||
|
for (neighbor, mut neighbor_path) in neighbors {
|
||||||
|
if visited.contains(&neighbor) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if neighbor.id.relation.is_none() {
|
||||||
|
expanded_verticies.push((neighbor, neighbor_path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
neighbor_path.push(neighbor.clone());
|
||||||
|
|
||||||
|
next_neighbors.append(
|
||||||
|
&mut neighbor
|
||||||
|
.edges_in
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.map(|v| (v.clone(), neighbor_path.clone()))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
|
visited.insert(neighbor);
|
||||||
|
}
|
||||||
|
neighbors = next_neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded_verticies
|
||||||
|
.into_iter()
|
||||||
|
.map(|(v, path)| {
|
||||||
|
(
|
||||||
|
RObject(v.id.clone()),
|
||||||
|
path.into_iter().map(|w| RSet(w.id.clone())).collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn write_savefile(&self, writeable: &mut (impl AsyncWriteExt + Unpin)) {
|
pub async fn write_savefile(&self, writeable: &mut (impl AsyncWriteExt + Unpin)) {
|
||||||
let mut current: (String, String) = (String::new(), String::new());
|
let mut current: (String, String) = (String::new(), String::new());
|
||||||
for node in self.nodes.read().await.iter() {
|
for vertex in self.verticies.read().await.iter() {
|
||||||
if current != (node.id.namespace.clone(), node.id.id.clone()) {
|
if current != (vertex.id.namespace.clone(), vertex.id.id.clone()) {
|
||||||
current = (node.id.namespace.clone(), node.id.id.clone());
|
current = (vertex.id.namespace.clone(), vertex.id.id.clone());
|
||||||
writeable.write_all("\n".as_bytes()).await.unwrap();
|
writeable.write_all("\n".as_bytes()).await.unwrap();
|
||||||
writeable
|
writeable
|
||||||
.write_all(format!("[{}:{}]\n", ¤t.0, ¤t.1).as_bytes())
|
.write_all(format!("[{}:{}]\n", ¤t.0, ¤t.1).as_bytes())
|
||||||
|
@ -171,24 +362,29 @@ impl RelationGraph {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let srcs = node
|
let srcs = vertex
|
||||||
.edges_in
|
.edges_in
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|x| x.id.id != WILDCARD_ID)
|
||||||
.map(|src| {
|
.map(|src| {
|
||||||
if src.id.namespace == current.0 && src.id.id == current.1 {
|
let obj = if src.id.namespace == current.0 && src.id.id == current.1 {
|
||||||
"self".to_string()
|
"self".to_string()
|
||||||
} else if let Some(rel) = &src.id.relation {
|
|
||||||
format!("{}:{}#{}", &src.id.namespace, &src.id.id, &rel)
|
|
||||||
} else {
|
} else {
|
||||||
format!("{}:{}", &src.id.namespace, &src.id.id)
|
format!("{}:{}", &src.id.namespace, &src.id.id)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rel) = &src.id.relation {
|
||||||
|
format!("{}#{}", &obj, &rel)
|
||||||
|
} else {
|
||||||
|
obj
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce(|acc, x| acc + ", " + &x)
|
.reduce(|acc, x| acc + ", " + &x)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let Some(rel) = &node.id.relation {
|
if let Some(rel) = &vertex.id.relation {
|
||||||
writeable
|
writeable
|
||||||
.write_all(format!("{} = [ {} ]\n", &rel, &srcs).as_bytes())
|
.write_all(format!("{} = [ {} ]\n", &rel, &srcs).as_bytes())
|
||||||
.await
|
.await
|
||||||
|
@ -199,15 +395,15 @@ impl RelationGraph {
|
||||||
pub async fn read_savefile(readable: &mut (impl AsyncBufReadExt + Unpin)) -> Self {
|
pub async fn read_savefile(readable: &mut (impl AsyncBufReadExt + Unpin)) -> Self {
|
||||||
let mut lines = readable.lines();
|
let mut lines = readable.lines();
|
||||||
let graph = Self::default();
|
let graph = Self::default();
|
||||||
let mut node: Option<(String, String)> = None;
|
let mut vertex: Option<(String, String)> = None;
|
||||||
while let Ok(Some(line)) = lines.next_line().await {
|
while let Ok(Some(line)) = lines.next_line().await {
|
||||||
if line.starts_with('[') && line.ends_with(']') {
|
if line.starts_with('[') && line.ends_with(']') {
|
||||||
let line = &mut line[1..line.len() - 1].split(':');
|
let line = &mut line[1..line.len() - 1].split(':');
|
||||||
let namespace = line.next().unwrap();
|
let namespace = line.next().unwrap();
|
||||||
let id = line.next().unwrap();
|
let id = line.next().unwrap();
|
||||||
node = Some((namespace.to_string(), id.to_string()));
|
vertex = Some((namespace.to_string(), id.to_string()));
|
||||||
} else if line.contains('=') && line.contains('[') && line.contains(']') {
|
} else if line.contains('=') && line.contains('[') && line.contains(']') {
|
||||||
if let Some(dst) = &node {
|
if let Some(dst) = &vertex {
|
||||||
let equals_pos = line.find('=').unwrap();
|
let equals_pos = line.find('=').unwrap();
|
||||||
let arr_start = line.find('[').unwrap();
|
let arr_start = line.find('[').unwrap();
|
||||||
let arr_stop = line.find(']').unwrap();
|
let arr_stop = line.find(']').unwrap();
|
||||||
|
@ -216,7 +412,7 @@ impl RelationGraph {
|
||||||
let arr = line[arr_start + 1..arr_stop].trim().split(", ");
|
let arr = line[arr_start + 1..arr_stop].trim().split(", ");
|
||||||
|
|
||||||
for obj in arr {
|
for obj in arr {
|
||||||
let src: NodeId = if obj.contains('#') {
|
let src: RObjectOrSet = if obj.contains('#') {
|
||||||
let sep_1 = obj.find(':');
|
let sep_1 = obj.find(':');
|
||||||
let sep_2 = obj.find('#').unwrap();
|
let sep_2 = obj.find('#').unwrap();
|
||||||
|
|
||||||
|
@ -228,7 +424,7 @@ impl RelationGraph {
|
||||||
|
|
||||||
let rel = &obj[sep_2 + 1..];
|
let rel = &obj[sep_2 + 1..];
|
||||||
|
|
||||||
(namespace, id, rel).into()
|
RObjectOrSet::Set(Cow::Owned((namespace, id, rel).into()))
|
||||||
} else {
|
} else {
|
||||||
let sep_1 = obj.find(':');
|
let sep_1 = obj.find(':');
|
||||||
|
|
||||||
|
@ -237,11 +433,11 @@ impl RelationGraph {
|
||||||
} else {
|
} else {
|
||||||
(dst.0.as_str(), dst.1.as_str())
|
(dst.0.as_str(), dst.1.as_str())
|
||||||
};
|
};
|
||||||
(namespace, id).into()
|
RObjectOrSet::Object(Cow::Owned((namespace, id).into()))
|
||||||
};
|
};
|
||||||
|
|
||||||
graph
|
graph
|
||||||
.insert(src, (dst.0.as_str(), dst.1.as_str(), rel))
|
.insert(src, &(dst.0.as_str(), dst.1.as_str(), rel).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,103 +447,159 @@ impl RelationGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Node {
|
impl Debug for Vertex {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Node").field("id", &self.id).finish()
|
f.debug_struct("vertex").field("id", &self.id).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_edge(from: Arc<Node>, to: Arc<Node>) {
|
async fn add_edge(from: Arc<Vertex>, to: Arc<Vertex>) {
|
||||||
from.edges_out.write().await.push(to.clone());
|
if !from.edges_out.read().await.contains(&to) {
|
||||||
to.edges_in.write().await.push(from);
|
from.edges_out.write().await.insert(to.clone());
|
||||||
|
}
|
||||||
|
if !to.edges_in.read().await.contains(&from) {
|
||||||
|
to.edges_in.write().await.insert(from);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Borrow<NodeId> for Arc<Node> {
|
impl Borrow<VertexId> for Arc<Vertex> {
|
||||||
fn borrow(&self) -> &NodeId {
|
fn borrow(&self) -> &VertexId {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Node {
|
impl PartialEq for Vertex {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.id == other.id
|
self.id == other.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Eq for Node {}
|
impl Eq for Vertex {}
|
||||||
|
|
||||||
impl PartialOrd for Node {
|
impl PartialOrd for Vertex {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Ord for Node {
|
impl Ord for Vertex {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
self.id.cmp(&other.id)
|
self.id.cmp(&other.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Node {
|
impl Hash for Vertex {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.id.hash(state);
|
self.id.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(&str, &str)> for NodeId {
|
impl From<(&str, &str)> for RObject {
|
||||||
fn from(value: (&str, &str)) -> Self {
|
fn from(value: (&str, &str)) -> Self {
|
||||||
Self {
|
Self(VertexId {
|
||||||
namespace: value.0.to_string(),
|
namespace: value.0.to_string(),
|
||||||
id: value.1.to_string(),
|
id: value.1.to_string(),
|
||||||
relation: None,
|
relation: None,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(&str, &str, &str)> for NodeId {
|
impl From<(String, String)> for RObject {
|
||||||
|
fn from(value: (String, String)) -> Self {
|
||||||
|
Self(VertexId {
|
||||||
|
namespace: value.0,
|
||||||
|
id: value.1,
|
||||||
|
relation: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, &str)> for RSet {
|
||||||
fn from(value: (&str, &str, &str)) -> Self {
|
fn from(value: (&str, &str, &str)) -> Self {
|
||||||
Self {
|
Self(VertexId {
|
||||||
namespace: value.0.to_string(),
|
namespace: value.0.to_string(),
|
||||||
id: value.1.to_string(),
|
id: value.1.to_string(),
|
||||||
relation: Some(value.2.to_string()),
|
relation: Some(value.2.to_string()),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(&str, &str, Option<&str>)> for NodeId {
|
impl From<(String, String, String)> for RSet {
|
||||||
fn from(value: (&str, &str, Option<&str>)) -> Self {
|
|
||||||
Self {
|
|
||||||
namespace: value.0.to_string(),
|
|
||||||
id: value.1.to_string(),
|
|
||||||
relation: value.2.map(|x| x.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(String, String)> for NodeId {
|
|
||||||
fn from(value: (String, String)) -> Self {
|
|
||||||
Self {
|
|
||||||
namespace: value.0,
|
|
||||||
id: value.1,
|
|
||||||
relation: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(String, String, String)> for NodeId {
|
|
||||||
fn from(value: (String, String, String)) -> Self {
|
fn from(value: (String, String, String)) -> Self {
|
||||||
Self {
|
Self(VertexId {
|
||||||
namespace: value.0,
|
namespace: value.0,
|
||||||
id: value.1,
|
id: value.1,
|
||||||
relation: Some(value.2),
|
relation: Some(value.2),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&str, &str, Option<&str>)> for RObjectOrSet<'_> {
|
||||||
|
fn from(value: (&str, &str, Option<&str>)) -> Self {
|
||||||
|
match value.2 {
|
||||||
|
Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())),
|
||||||
|
None => Self::Object(Cow::Owned((value.0, value.1).into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(String, String, Option<String>)> for NodeId {
|
impl From<(String, String, Option<String>)> for RObjectOrSet<'_> {
|
||||||
fn from(value: (String, String, Option<String>)) -> Self {
|
fn from(value: (String, String, Option<String>)) -> Self {
|
||||||
Self {
|
match value.2 {
|
||||||
namespace: value.0,
|
Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())),
|
||||||
id: value.1,
|
None => Self::Object(Cow::Owned((value.0, value.1).into())),
|
||||||
relation: value.2,
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<VertexId> for RSet {
|
||||||
|
fn borrow(&self) -> &VertexId {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<VertexId> for RSet {
|
||||||
|
fn eq(&self, other: &VertexId) -> bool {
|
||||||
|
self.0.eq(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialEq<RSet> for VertexId {
|
||||||
|
fn eq(&self, other: &RSet) -> bool {
|
||||||
|
self.eq(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<VertexId> for RObject {
|
||||||
|
fn borrow(&self) -> &VertexId {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RSet> for RObjectOrSet<'_> {
|
||||||
|
fn from(value: RSet) -> Self {
|
||||||
|
Self::Set(Cow::Owned(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<RObject> for RObjectOrSet<'_> {
|
||||||
|
fn from(value: RObject) -> Self {
|
||||||
|
Self::Object(Cow::Owned(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a RSet> for RObjectOrSet<'a> {
|
||||||
|
fn from(value: &'a RSet) -> Self {
|
||||||
|
Self::Set(Cow::Borrowed(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> From<&'a RObject> for RObjectOrSet<'a> {
|
||||||
|
fn from(value: &'a RObject) -> Self {
|
||||||
|
Self::Object(Cow::Borrowed(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a RObjectOrSet<'a>> for RObjectOrSet<'a> {
|
||||||
|
fn from(value: &'a RObjectOrSet<'a>) -> Self {
|
||||||
|
match value {
|
||||||
|
Self::Object(obj) => Self::Object(Cow::Borrowed(obj.borrow())),
|
||||||
|
Self::Set(set) => Self::Set(Cow::Borrowed(set.borrow())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,71 @@
|
||||||
//hello world
|
use crate::{RObject, RSet, RelationGraph, WILDCARD_ID};
|
||||||
|
|
||||||
use crate::{Distanced, NodeId, RelationGraph};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn distanced_ordering() {
|
|
||||||
let a = Distanced::new((), 0);
|
|
||||||
let b = Distanced::one(());
|
|
||||||
let c = Distanced::new((), 1);
|
|
||||||
let d = Distanced::new((), 2);
|
|
||||||
|
|
||||||
assert!(a < b);
|
|
||||||
assert!(b == c);
|
|
||||||
assert!(c < d);
|
|
||||||
assert!(a < d);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn simple_graph() {
|
async fn simple_graph() {
|
||||||
let graph = RelationGraph::default();
|
let graph = RelationGraph::default();
|
||||||
|
|
||||||
let alice = ("user", "alice");
|
let alice: RObject = ("user", "alice").into();
|
||||||
let bob = ("user", "bob");
|
let bob: RObject = ("user", "bob").into();
|
||||||
let charlie = ("user", "charlie");
|
let charlie: RObject = ("user", "charlie").into();
|
||||||
|
|
||||||
let foo_read = ("application", "foo", "read");
|
let foo_read: RSet = ("application", "foo", "read").into();
|
||||||
let bar_read = ("application", "bar", "read");
|
let bar_read: RSet = ("application", "bar", "read").into();
|
||||||
|
|
||||||
graph.insert(alice, foo_read).await;
|
graph.insert(&alice, &foo_read).await;
|
||||||
graph.insert(bob, bar_read).await;
|
graph.insert(&bob, &bar_read).await;
|
||||||
|
|
||||||
assert!(graph.has_recursive(alice, foo_read, None).await);
|
assert!(graph.check(&alice, &foo_read, None).await);
|
||||||
assert!(!graph.has_recursive(alice, bar_read, None).await);
|
assert!(!graph.check(&alice, &bar_read, None).await);
|
||||||
|
|
||||||
assert!(!graph.has_recursive(bob, foo_read, None).await);
|
assert!(!graph.check(&bob, &foo_read, None).await);
|
||||||
assert!(graph.has_recursive(bob, bar_read, None).await);
|
assert!(graph.check(&bob, &bar_read, None).await);
|
||||||
|
|
||||||
assert!(!graph.has_recursive(charlie, foo_read, None).await);
|
assert!(!graph.check(&charlie, &foo_read, None).await);
|
||||||
assert!(!graph.has_recursive(charlie, bar_read, None).await);
|
assert!(!graph.check(&charlie, &bar_read, None).await);
|
||||||
|
|
||||||
graph.remove(alice, foo_read).await;
|
graph.remove(&alice, &foo_read).await;
|
||||||
graph.remove(alice, bar_read).await;
|
graph.remove(&alice, &bar_read).await;
|
||||||
|
|
||||||
assert!(!graph.has_recursive(alice, foo_read, None).await);
|
assert!(!graph.check(&alice, &foo_read, None).await);
|
||||||
assert!(!graph.has_recursive(alice, bar_read, None).await);
|
assert!(!graph.check(&alice, &bar_read, None).await);
|
||||||
|
|
||||||
graph.insert(charlie, foo_read).await;
|
graph.insert(&charlie, &foo_read).await;
|
||||||
graph.insert(charlie, bar_read).await;
|
graph.insert(&charlie, &bar_read).await;
|
||||||
|
|
||||||
assert!(graph.has_recursive(charlie, foo_read, None).await);
|
assert!(graph.check(&charlie, &foo_read, None).await);
|
||||||
assert!(graph.has_recursive(charlie, bar_read, None).await);
|
assert!(graph.check(&charlie, &bar_read, None).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn wildcard() {
|
||||||
|
let graph = RelationGraph::default();
|
||||||
|
|
||||||
|
let alice: RObject = ("user", "alice").into();
|
||||||
|
let bob: RObject = ("user", "bob").into();
|
||||||
|
let charlie: RObject = ("user", "charlie").into();
|
||||||
|
|
||||||
|
let user_wildcard: RObject = ("user", WILDCARD_ID).into();
|
||||||
|
|
||||||
|
let foo_read: RSet = ("application", "foo", "read").into();
|
||||||
|
let bar_read: RSet = ("application", "bar", "read").into();
|
||||||
|
|
||||||
|
let app_read: RSet = ("application", WILDCARD_ID, "read").into();
|
||||||
|
|
||||||
|
let some_app_read: RSet = ("application", "bla", "read").into();
|
||||||
|
|
||||||
|
graph.insert(&alice, &foo_read).await;
|
||||||
|
graph.insert(&user_wildcard, &foo_read).await;
|
||||||
|
graph.insert(&bob, &bar_read).await;
|
||||||
|
|
||||||
|
assert!(graph.check(&alice, &foo_read, None).await);
|
||||||
|
assert!(graph.check(&bob, &foo_read, None).await);
|
||||||
|
assert!(graph.check(&charlie, &foo_read, None).await);
|
||||||
|
assert!(graph.check(&bob, &bar_read, None).await);
|
||||||
|
|
||||||
|
graph.insert(&alice, &app_read).await;
|
||||||
|
|
||||||
|
assert!(graph.check(&alice, &some_app_read, None).await);
|
||||||
|
assert!(graph.check(&alice, &bar_read, None).await);
|
||||||
|
assert!(!graph.check(&bob, &some_app_read, None).await);
|
||||||
|
assert!(!graph.check(&charlie, &some_app_read, None).await);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,40 +6,71 @@ service RebacService {
|
||||||
rpc Revoke(RevokeReq) returns (RevokeRes);
|
rpc Revoke(RevokeReq) returns (RevokeRes);
|
||||||
rpc Exists(ExistsReq) returns (ExistsRes);
|
rpc Exists(ExistsReq) returns (ExistsRes);
|
||||||
rpc IsPermitted(IsPermittedReq) returns (IsPermittedRes);
|
rpc IsPermitted(IsPermittedReq) returns (IsPermittedRes);
|
||||||
|
rpc Expand(ExpandReq) returns (ExpandRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message GrantReq{
|
message GrantReq{
|
||||||
Object src = 1;
|
oneof src {
|
||||||
Object dst = 2;
|
Object src_obj = 1;
|
||||||
|
Set src_set = 2;
|
||||||
|
}
|
||||||
|
Set dst = 3;
|
||||||
}
|
}
|
||||||
message GrantRes{}
|
message GrantRes{}
|
||||||
|
|
||||||
message RevokeReq{
|
message RevokeReq{
|
||||||
Object src = 1;
|
oneof src {
|
||||||
Object dst = 2;
|
Object src_obj = 1;
|
||||||
|
Set src_set = 2;
|
||||||
|
}
|
||||||
|
Set dst = 3;
|
||||||
}
|
}
|
||||||
message RevokeRes{}
|
message RevokeRes{}
|
||||||
|
|
||||||
message ExistsReq{
|
message ExistsReq{
|
||||||
Object src = 1;
|
oneof src {
|
||||||
Object dst = 2;
|
Object src_obj = 1;
|
||||||
|
Set src_set = 2;
|
||||||
|
}
|
||||||
|
Set dst = 3;
|
||||||
}
|
}
|
||||||
message ExistsRes{
|
message ExistsRes{
|
||||||
bool exists = 1;
|
bool exists = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IsPermittedReq{
|
message IsPermittedReq{
|
||||||
Object src = 1;
|
oneof src {
|
||||||
Object dst = 2;
|
Object src_obj = 1;
|
||||||
|
Set src_set = 2;
|
||||||
|
}
|
||||||
|
Set dst = 3;
|
||||||
}
|
}
|
||||||
message IsPermittedRes{
|
message IsPermittedRes{
|
||||||
bool permitted = 1;
|
bool permitted = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ExpandReq {
|
||||||
|
Set dst = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExpandRes {
|
||||||
|
repeated ExpandResItem expanded = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExpandResItem {
|
||||||
|
Object src = 1;
|
||||||
|
repeated Set path = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
message Object{
|
message Object{
|
||||||
string namespace = 1;
|
string namespace = 1;
|
||||||
string id = 2;
|
string id = 2;
|
||||||
optional string relation = 3;
|
}
|
||||||
|
|
||||||
|
message Set{
|
||||||
|
string namespace = 1;
|
||||||
|
string id = 2;
|
||||||
|
string relation = 3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use jsonwebtoken::{decode, DecodingKey, TokenData, Validation};
|
use jsonwebtoken::{decode, DecodingKey, TokenData, Validation};
|
||||||
use log::info;
|
use log::info;
|
||||||
use rebacs_core::{NodeId, RelationGraph};
|
use rebacs_core::{RObject, RObjectOrSet, RSet, RelationGraph};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tonic::metadata::MetadataMap;
|
use tonic::metadata::MetadataMap;
|
||||||
use tonic::{Request, Response, Status};
|
use tonic::{Request, Response, Status};
|
||||||
|
|
||||||
use crate::rebacs_proto::Object;
|
|
||||||
use crate::rebacs_proto::{
|
use crate::rebacs_proto::{
|
||||||
rebac_service_server, ExistsReq, ExistsRes, GrantReq, GrantRes, IsPermittedReq, IsPermittedRes,
|
exists_req, grant_req, is_permitted_req, rebac_service_server, revoke_req, ExistsReq,
|
||||||
RevokeReq, RevokeRes,
|
ExistsRes, ExpandReq, ExpandRes, ExpandResItem, GrantReq, GrantRes, IsPermittedReq,
|
||||||
|
IsPermittedRes, Object, RevokeReq, RevokeRes, Set,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -22,37 +22,63 @@ pub struct RebacService {
|
||||||
pub save_trigger: Sender<()>,
|
pub save_trigger: Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const NAMESPACE_NS: &str = "namespace";
|
|
||||||
const USER_NS: &str = "user";
|
const USER_NS: &str = "user";
|
||||||
const GRANT_RELATION: &str = "grant";
|
|
||||||
const REVOKE_RELATION: &str = "revoke";
|
macro_rules! extract {
|
||||||
|
($type:ident::Src($src:expr)) => {{
|
||||||
|
let src = $src
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(Status::invalid_argument("src must be set"))?;
|
||||||
|
let src: RObjectOrSet = match src {
|
||||||
|
$type::Src::SrcObj(obj) => (obj.namespace.clone(), obj.id.clone(), None).into(),
|
||||||
|
$type::Src::SrcSet(set) => (
|
||||||
|
set.namespace.clone(),
|
||||||
|
set.id.clone(),
|
||||||
|
Some(set.relation.clone()),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if src.namespace().is_empty() && src.id().is_empty() {
|
||||||
|
None
|
||||||
|
} else if src.namespace().is_empty() {
|
||||||
|
return Err(Status::invalid_argument("src.namespace must be set"));
|
||||||
|
} else if src.id().is_empty() {
|
||||||
|
return Err(Status::invalid_argument("src.id must be set"));
|
||||||
|
} else {
|
||||||
|
Some(src)
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl rebac_service_server::RebacService for RebacService {
|
impl rebac_service_server::RebacService for RebacService {
|
||||||
async fn grant(&self, request: Request<GrantReq>) -> Result<Response<GrantRes>, Status> {
|
async fn grant(&self, request: Request<GrantReq>) -> Result<Response<GrantRes>, Status> {
|
||||||
let token =
|
let token =
|
||||||
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
||||||
|
let user: RObject = (USER_NS, token.claims.sub.as_str()).into();
|
||||||
|
|
||||||
let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
|
let src = extract!(grant_req::Src(request.get_ref().src))
|
||||||
|
.unwrap_or(("user", token.claims.sub.as_str(), None).into());
|
||||||
|
let dst = extract_dst(request.get_ref().dst.as_ref())?;
|
||||||
|
|
||||||
if !is_permitted(&token, &dst, GRANT_RELATION, &self.graph).await {
|
if !self.graph.can_write(&user, &dst, None).await {
|
||||||
return Err(Status::permission_denied(
|
return Err(Status::permission_denied(
|
||||||
"token not permitted to grant permissions on dst",
|
"token not permitted to grant permissions on dst",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"created relation {}:{}#{}@{}:{}#{} for {}",
|
"created relation {}:{}#{}@{}:{}#{} for {}",
|
||||||
dst.namespace,
|
dst.namespace(),
|
||||||
dst.id,
|
dst.id(),
|
||||||
dst.relation.clone().unwrap_or_default(),
|
dst.relation(),
|
||||||
src.namespace,
|
src.namespace(),
|
||||||
src.id,
|
src.id(),
|
||||||
src.relation.clone().unwrap_or_default(),
|
src.relation().map(|x| x.to_string()).unwrap_or_default(),
|
||||||
token.claims.sub
|
token.claims.sub
|
||||||
);
|
);
|
||||||
|
|
||||||
self.graph.insert(src, dst).await;
|
self.graph.insert(src, &dst).await;
|
||||||
|
|
||||||
self.save_trigger.send(()).await.unwrap();
|
self.save_trigger.send(()).await.unwrap();
|
||||||
|
|
||||||
|
@ -61,34 +87,28 @@ impl rebac_service_server::RebacService for RebacService {
|
||||||
async fn revoke(&self, request: Request<RevokeReq>) -> Result<Response<RevokeRes>, Status> {
|
async fn revoke(&self, request: Request<RevokeReq>) -> Result<Response<RevokeRes>, Status> {
|
||||||
let token =
|
let token =
|
||||||
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
||||||
|
let user: RObject = (USER_NS, token.claims.sub.as_str()).into();
|
||||||
|
|
||||||
let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
|
let src = extract!(revoke_req::Src(request.get_ref().src))
|
||||||
|
.unwrap_or(("user", token.claims.sub.as_str(), None).into());
|
||||||
|
let dst = extract_dst(request.get_ref().dst.as_ref())?;
|
||||||
|
|
||||||
if !is_permitted(&token, &dst, REVOKE_RELATION, &self.graph).await {
|
if !self.graph.can_write(&user, &dst, None).await {
|
||||||
return Err(Status::permission_denied(
|
return Err(Status::permission_denied(
|
||||||
"token not permitted to revoke permissions on dst",
|
"token not permitted to revoke permissions on dst",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.graph
|
self.graph.remove(&src, &dst).await;
|
||||||
.remove(
|
|
||||||
(
|
|
||||||
src.namespace.to_string(),
|
|
||||||
src.id.to_string(),
|
|
||||||
src.relation.clone(),
|
|
||||||
),
|
|
||||||
(dst.namespace.clone(), dst.id.clone(), dst.relation.clone()),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"delted relation {}:{}#{}@{}:{}#{} for {}",
|
"delted relation {}:{}#{}@{}:{}#{} for {}",
|
||||||
dst.namespace,
|
dst.namespace(),
|
||||||
dst.id,
|
dst.id(),
|
||||||
dst.relation.clone().unwrap_or_default(),
|
dst.relation(),
|
||||||
src.namespace,
|
src.namespace(),
|
||||||
src.id,
|
src.id(),
|
||||||
src.relation.clone().unwrap_or_default(),
|
src.relation().map(|x| x.to_string()).unwrap_or_default(),
|
||||||
token.claims.sub
|
token.claims.sub
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -100,9 +120,11 @@ impl rebac_service_server::RebacService for RebacService {
|
||||||
let token =
|
let token =
|
||||||
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
||||||
|
|
||||||
let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
|
let src = extract!(exists_req::Src(request.get_ref().src))
|
||||||
|
.unwrap_or(("user", token.claims.sub.as_str(), None).into());
|
||||||
|
let dst = extract_dst(request.get_ref().dst.as_ref())?;
|
||||||
|
|
||||||
let exists = self.graph.has(src, dst).await;
|
let exists = self.graph.has(src, &dst).await;
|
||||||
|
|
||||||
Ok(Response::new(ExistsRes { exists }))
|
Ok(Response::new(ExistsRes { exists }))
|
||||||
}
|
}
|
||||||
|
@ -114,12 +136,50 @@ impl rebac_service_server::RebacService for RebacService {
|
||||||
let token =
|
let token =
|
||||||
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
||||||
|
|
||||||
let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
|
let src = extract!(is_permitted_req::Src(request.get_ref().src))
|
||||||
|
.unwrap_or(("user", token.claims.sub.as_str(), None).into());
|
||||||
|
let dst = extract_dst(request.get_ref().dst.as_ref())?;
|
||||||
|
|
||||||
let permitted = self.graph.has_recursive(src, dst, None).await;
|
let permitted = self.graph.check(src, &dst, None).await;
|
||||||
|
|
||||||
Ok(Response::new(IsPermittedRes { permitted }))
|
Ok(Response::new(IsPermittedRes { permitted }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn expand(&self, request: Request<ExpandReq>) -> Result<Response<ExpandRes>, Status> {
|
||||||
|
let token =
|
||||||
|
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
|
||||||
|
let dst = extract_dst(request.get_ref().dst.as_ref())?;
|
||||||
|
|
||||||
|
let user: RObject = (USER_NS, token.claims.sub.as_str()).into();
|
||||||
|
if !self.graph.can_write(&user, &dst, None).await {
|
||||||
|
return Err(Status::permission_denied(
|
||||||
|
"token not permitted to expand permissions on dst",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let expanded = self
|
||||||
|
.graph
|
||||||
|
.expand(&dst)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.map(|(v, path)| ExpandResItem {
|
||||||
|
src: Some(Object {
|
||||||
|
namespace: v.namespace().to_string(),
|
||||||
|
id: v.id().to_string(),
|
||||||
|
}),
|
||||||
|
path: path
|
||||||
|
.into_iter()
|
||||||
|
.map(|w| Set {
|
||||||
|
namespace: w.namespace().to_string(),
|
||||||
|
id: w.id().to_string(),
|
||||||
|
relation: w.relation().to_string(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Response::new(ExpandRes { expanded }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -154,54 +214,18 @@ async fn extract_token(
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_permitted(
|
fn extract_dst(dst: Option<&Set>) -> Result<RSet, Status> {
|
||||||
token: &TokenData<Claims>,
|
|
||||||
dst: &NodeId,
|
|
||||||
relation: &str,
|
|
||||||
graph: &RelationGraph,
|
|
||||||
) -> bool {
|
|
||||||
let s1 = graph
|
|
||||||
.has_recursive(
|
|
||||||
(USER_NS, token.claims.sub.as_str()),
|
|
||||||
(dst.namespace.as_str(), dst.id.as_str(), relation),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let s2 = graph
|
|
||||||
.has_recursive(
|
|
||||||
(USER_NS, token.claims.sub.as_str()),
|
|
||||||
(NAMESPACE_NS, dst.namespace.as_str(), relation),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
s1 || s2
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_src_dst(src: &Option<Object>, dst: &Option<Object>) -> Result<(NodeId, NodeId), Status> {
|
|
||||||
let src = src
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(Status::invalid_argument("src must be set"))?;
|
|
||||||
let src: NodeId = (src.namespace.clone(), src.id.clone(), src.relation.clone()).into();
|
|
||||||
let dst = dst
|
let dst = dst
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(Status::invalid_argument("dst must be set"))?;
|
.ok_or(Status::invalid_argument("dst must be set"))?;
|
||||||
let dst: NodeId = (dst.namespace.clone(), dst.id.clone(), dst.relation.clone()).into();
|
let dst: RSet = (dst.namespace.clone(), dst.id.clone(), dst.relation.clone()).into();
|
||||||
|
|
||||||
if dst.namespace.is_empty() {
|
if dst.namespace().is_empty() {
|
||||||
return Err(Status::invalid_argument("dst.namespace must be set"));
|
return Err(Status::invalid_argument("dst.namespace must be set"));
|
||||||
}
|
}
|
||||||
if dst.id.is_empty() {
|
if dst.id().is_empty() {
|
||||||
return Err(Status::invalid_argument("dst.id must be set"));
|
return Err(Status::invalid_argument("dst.id must be set"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if src.namespace.is_empty() {
|
Ok(dst)
|
||||||
return Err(Status::invalid_argument("src.namespace must be set"));
|
|
||||||
}
|
|
||||||
if src.id.is_empty() {
|
|
||||||
return Err(Status::invalid_argument("src.id must be set"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((src, dst))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,10 @@ use tokio::{
|
||||||
use tonic::transport::Server;
|
use tonic::transport::Server;
|
||||||
|
|
||||||
pub mod grpc_service;
|
pub mod grpc_service;
|
||||||
pub mod rebacs_proto;
|
pub mod rebacs_proto {
|
||||||
|
|
||||||
|
tonic::include_proto!("eu.zettoit.rebacs");
|
||||||
|
}
|
||||||
|
|
||||||
use crate::rebacs_proto::rebac_service_server;
|
use crate::rebacs_proto::rebac_service_server;
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
tonic::include_proto!("eu.zettoit.rebacs");
|
|
Loading…
Reference in a new issue