mirror of
https://github.com/pfzetto/rebacs
synced 2024-11-23 03:32:49 +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::{
|
||||
borrow::Borrow,
|
||||
borrow::{Borrow, Cow},
|
||||
cmp::Ordering,
|
||||
collections::{BTreeSet, HashSet},
|
||||
fmt::Debug,
|
||||
|
@ -15,87 +15,183 @@ use tokio::{
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct NodeId {
|
||||
pub namespace: String,
|
||||
pub id: String,
|
||||
pub relation: Option<String>,
|
||||
const WILDCARD_ID: &str = "*";
|
||||
const WRITE_RELATION: &str = "grant";
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||
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 {
|
||||
pub id: NodeId,
|
||||
pub edges_in: RwLock<Vec<Arc<Node>>>,
|
||||
pub edges_out: RwLock<Vec<Arc<Node>>>,
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RObjectOrSet<'a> {
|
||||
Object(Cow<'a, RObject>),
|
||||
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)]
|
||||
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 {
|
||||
pub async fn insert(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) {
|
||||
let src = src.into();
|
||||
let dst = dst.into();
|
||||
pub async fn insert(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) {
|
||||
let src: RObjectOrSet<'_> = src.into();
|
||||
let mut verticies = self.verticies.write().await;
|
||||
|
||||
let mut nodes = self.nodes.write().await;
|
||||
|
||||
let src_node = match nodes.get(&src) {
|
||||
Some(node) => node.clone(),
|
||||
let mut get_or_create = |vertex: &VertexId| match verticies.get(vertex) {
|
||||
Some(vertex) => vertex.clone(),
|
||||
None => {
|
||||
let node = Arc::new(Node {
|
||||
id: src,
|
||||
edges_out: RwLock::new(vec![]),
|
||||
edges_in: RwLock::new(vec![]),
|
||||
let vertex = Arc::new(Vertex {
|
||||
id: vertex.clone(),
|
||||
edges_out: RwLock::new(HashSet::new()),
|
||||
edges_in: RwLock::new(HashSet::new()),
|
||||
});
|
||||
nodes.insert(node.clone());
|
||||
node
|
||||
verticies.insert(vertex.clone());
|
||||
vertex
|
||||
}
|
||||
};
|
||||
let dst_node = match nodes.get(&dst).cloned() {
|
||||
Some(node) => node.clone(),
|
||||
None => {
|
||||
let node = Arc::new(Node {
|
||||
id: dst,
|
||||
edges_out: RwLock::new(vec![]),
|
||||
edges_in: RwLock::new(vec![]),
|
||||
});
|
||||
nodes.insert(node.clone());
|
||||
node
|
||||
}
|
||||
};
|
||||
add_edge(src_node, dst_node).await;
|
||||
|
||||
let src_without_relation = src.relation().is_none();
|
||||
|
||||
let src_wildcard: RObjectOrSet = (src.namespace(), WILDCARD_ID, src.relation()).into();
|
||||
let src_wildcard = get_or_create(src_wildcard.vertex_id());
|
||||
let src_vertex = get_or_create(src.vertex_id());
|
||||
|
||||
let dst_wildcard: RSet = (dst.namespace(), WILDCARD_ID, dst.relation()).into();
|
||||
let dst_wildcard = get_or_create(dst_wildcard.vertex_id());
|
||||
let dst_vertex = get_or_create(dst.vertex_id());
|
||||
|
||||
if src_without_relation && src_vertex.id.id != WILDCARD_ID {
|
||||
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>) {
|
||||
let src = src.into();
|
||||
let dst = dst.into();
|
||||
pub async fn remove(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) {
|
||||
let src: RObjectOrSet<'_> = src.into();
|
||||
let mut verticies = self.verticies.write().await;
|
||||
|
||||
let mut nodes = self.nodes.write().await;
|
||||
|
||||
let src = nodes.get(&src).cloned();
|
||||
let dst = nodes.get(&dst).cloned();
|
||||
let src = verticies.get(src.vertex_id()).cloned();
|
||||
let dst = verticies.get(dst.vertex_id()).cloned();
|
||||
|
||||
if let (Some(src), Some(dst)) = (src, dst) {
|
||||
src.edges_out.write().await.retain(|x| x != &dst);
|
||||
dst.edges_in.write().await.retain(|x| x != &src);
|
||||
|
||||
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() {
|
||||
nodes.remove(&dst.id);
|
||||
verticies.remove(&dst.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn has(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) -> bool {
|
||||
let src = src.into();
|
||||
let dst = dst.into();
|
||||
|
||||
/// does a edge from src to dst exist
|
||||
pub async fn has(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) -> bool {
|
||||
let src: RObjectOrSet<'_> = src.into();
|
||||
let (src, dst) = {
|
||||
let nodes = self.nodes.read().await;
|
||||
(nodes.get(&src).cloned(), nodes.get(&dst).cloned())
|
||||
let verticies = self.verticies.read().await;
|
||||
(
|
||||
verticies.get(src.vertex_id()).cloned(),
|
||||
verticies.get(dst.vertex_id()).cloned(),
|
||||
)
|
||||
};
|
||||
|
||||
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
|
||||
pub async fn has_recursive<'a>(
|
||||
pub async fn check<'a>(
|
||||
&self,
|
||||
src: impl Into<NodeId>,
|
||||
dst: impl Into<NodeId>,
|
||||
src: impl Into<RObjectOrSet<'_>>,
|
||||
dst: &RSet,
|
||||
limit: Option<u32>,
|
||||
) -> bool {
|
||||
let src = src.into();
|
||||
let dst = dst.into();
|
||||
|
||||
let src = if let Some(src) = self.nodes.read().await.get(&src) {
|
||||
src.clone()
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let src: RObjectOrSet<'_> = src.into();
|
||||
let mut distance = 1;
|
||||
|
||||
let mut neighbors = src
|
||||
.edges_out
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let mut neighbors: Vec<Arc<Vertex>> = if let Some(src) =
|
||||
self.verticies.read().await.get(src.vertex_id())
|
||||
{
|
||||
src.edges_out.read().await.iter().cloned().collect()
|
||||
} else {
|
||||
let wildcard_src: RObject = (src.namespace(), WILDCARD_ID).into();
|
||||
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() {
|
||||
if let Some(limit) = limit {
|
||||
if distance > limit {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut next_neighbors = vec![];
|
||||
for neighbor in neighbors {
|
||||
if distance > 1 && visited.contains(&neighbor) {
|
||||
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;
|
||||
}
|
||||
if let Some(limit) = limit {
|
||||
if distance > limit {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut node_neighbors = neighbor.edges_out.read().await.clone();
|
||||
next_neighbors.append(&mut node_neighbors);
|
||||
let mut vertex_neighbors =
|
||||
neighbor.edges_out.read().await.iter().cloned().collect();
|
||||
next_neighbors.append(&mut vertex_neighbors);
|
||||
|
||||
visited.insert(neighbor);
|
||||
}
|
||||
|
@ -159,11 +268,93 @@ impl RelationGraph {
|
|||
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)) {
|
||||
let mut current: (String, String) = (String::new(), String::new());
|
||||
for node in self.nodes.read().await.iter() {
|
||||
if current != (node.id.namespace.clone(), node.id.id.clone()) {
|
||||
current = (node.id.namespace.clone(), node.id.id.clone());
|
||||
for vertex in self.verticies.read().await.iter() {
|
||||
if current != (vertex.id.namespace.clone(), vertex.id.id.clone()) {
|
||||
current = (vertex.id.namespace.clone(), vertex.id.id.clone());
|
||||
writeable.write_all("\n".as_bytes()).await.unwrap();
|
||||
writeable
|
||||
.write_all(format!("[{}:{}]\n", ¤t.0, ¤t.1).as_bytes())
|
||||
|
@ -171,24 +362,29 @@ impl RelationGraph {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
let srcs = node
|
||||
let srcs = vertex
|
||||
.edges_in
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.filter(|x| x.id.id != WILDCARD_ID)
|
||||
.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()
|
||||
} else if let Some(rel) = &src.id.relation {
|
||||
format!("{}:{}#{}", &src.id.namespace, &src.id.id, &rel)
|
||||
} else {
|
||||
format!("{}:{}", &src.id.namespace, &src.id.id)
|
||||
};
|
||||
|
||||
if let Some(rel) = &src.id.relation {
|
||||
format!("{}#{}", &obj, &rel)
|
||||
} else {
|
||||
obj
|
||||
}
|
||||
})
|
||||
.reduce(|acc, x| acc + ", " + &x)
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(rel) = &node.id.relation {
|
||||
if let Some(rel) = &vertex.id.relation {
|
||||
writeable
|
||||
.write_all(format!("{} = [ {} ]\n", &rel, &srcs).as_bytes())
|
||||
.await
|
||||
|
@ -199,15 +395,15 @@ impl RelationGraph {
|
|||
pub async fn read_savefile(readable: &mut (impl AsyncBufReadExt + Unpin)) -> Self {
|
||||
let mut lines = readable.lines();
|
||||
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 {
|
||||
if line.starts_with('[') && line.ends_with(']') {
|
||||
let line = &mut line[1..line.len() - 1].split(':');
|
||||
let namespace = 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(']') {
|
||||
if let Some(dst) = &node {
|
||||
if let Some(dst) = &vertex {
|
||||
let equals_pos = line.find('=').unwrap();
|
||||
let arr_start = 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(", ");
|
||||
|
||||
for obj in arr {
|
||||
let src: NodeId = if obj.contains('#') {
|
||||
let src: RObjectOrSet = if obj.contains('#') {
|
||||
let sep_1 = obj.find(':');
|
||||
let sep_2 = obj.find('#').unwrap();
|
||||
|
||||
|
@ -228,7 +424,7 @@ impl RelationGraph {
|
|||
|
||||
let rel = &obj[sep_2 + 1..];
|
||||
|
||||
(namespace, id, rel).into()
|
||||
RObjectOrSet::Set(Cow::Owned((namespace, id, rel).into()))
|
||||
} else {
|
||||
let sep_1 = obj.find(':');
|
||||
|
||||
|
@ -237,11 +433,11 @@ impl RelationGraph {
|
|||
} else {
|
||||
(dst.0.as_str(), dst.1.as_str())
|
||||
};
|
||||
(namespace, id).into()
|
||||
RObjectOrSet::Object(Cow::Owned((namespace, id).into()))
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
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>) {
|
||||
from.edges_out.write().await.push(to.clone());
|
||||
to.edges_in.write().await.push(from);
|
||||
async fn add_edge(from: Arc<Vertex>, to: Arc<Vertex>) {
|
||||
if !from.edges_out.read().await.contains(&to) {
|
||||
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> {
|
||||
fn borrow(&self) -> &NodeId {
|
||||
impl Borrow<VertexId> for Arc<Vertex> {
|
||||
fn borrow(&self) -> &VertexId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
impl PartialEq for Vertex {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
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> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl Ord for Node {
|
||||
impl Ord for Vertex {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Node {
|
||||
impl Hash for Vertex {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, &str)> for NodeId {
|
||||
impl From<(&str, &str)> for RObject {
|
||||
fn from(value: (&str, &str)) -> Self {
|
||||
Self {
|
||||
Self(VertexId {
|
||||
namespace: value.0.to_string(),
|
||||
id: value.1.to_string(),
|
||||
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 {
|
||||
Self {
|
||||
Self(VertexId {
|
||||
namespace: value.0.to_string(),
|
||||
id: value.1.to_string(),
|
||||
relation: Some(value.2.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&str, &str, Option<&str>)> for NodeId {
|
||||
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 {
|
||||
impl From<(String, String, String)> for RSet {
|
||||
fn from(value: (String, String, String)) -> Self {
|
||||
Self {
|
||||
Self(VertexId {
|
||||
namespace: value.0,
|
||||
id: value.1,
|
||||
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 {
|
||||
Self {
|
||||
namespace: value.0,
|
||||
id: value.1,
|
||||
relation: value.2,
|
||||
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 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::{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);
|
||||
}
|
||||
use crate::{RObject, RSet, RelationGraph, WILDCARD_ID};
|
||||
|
||||
#[tokio::test]
|
||||
async fn simple_graph() {
|
||||
let graph = RelationGraph::default();
|
||||
|
||||
let alice = ("user", "alice");
|
||||
let bob = ("user", "bob");
|
||||
let charlie = ("user", "charlie");
|
||||
let alice: RObject = ("user", "alice").into();
|
||||
let bob: RObject = ("user", "bob").into();
|
||||
let charlie: RObject = ("user", "charlie").into();
|
||||
|
||||
let foo_read = ("application", "foo", "read");
|
||||
let bar_read = ("application", "bar", "read");
|
||||
let foo_read: RSet = ("application", "foo", "read").into();
|
||||
let bar_read: RSet = ("application", "bar", "read").into();
|
||||
|
||||
graph.insert(alice, foo_read).await;
|
||||
graph.insert(bob, bar_read).await;
|
||||
graph.insert(&alice, &foo_read).await;
|
||||
graph.insert(&bob, &bar_read).await;
|
||||
|
||||
assert!(graph.has_recursive(alice, foo_read, None).await);
|
||||
assert!(!graph.has_recursive(alice, bar_read, None).await);
|
||||
assert!(graph.check(&alice, &foo_read, None).await);
|
||||
assert!(!graph.check(&alice, &bar_read, None).await);
|
||||
|
||||
assert!(!graph.has_recursive(bob, foo_read, None).await);
|
||||
assert!(graph.has_recursive(bob, bar_read, None).await);
|
||||
assert!(!graph.check(&bob, &foo_read, None).await);
|
||||
assert!(graph.check(&bob, &bar_read, None).await);
|
||||
|
||||
assert!(!graph.has_recursive(charlie, foo_read, None).await);
|
||||
assert!(!graph.has_recursive(charlie, bar_read, None).await);
|
||||
assert!(!graph.check(&charlie, &foo_read, None).await);
|
||||
assert!(!graph.check(&charlie, &bar_read, None).await);
|
||||
|
||||
graph.remove(alice, foo_read).await;
|
||||
graph.remove(alice, bar_read).await;
|
||||
graph.remove(&alice, &foo_read).await;
|
||||
graph.remove(&alice, &bar_read).await;
|
||||
|
||||
assert!(!graph.has_recursive(alice, foo_read, None).await);
|
||||
assert!(!graph.has_recursive(alice, bar_read, None).await);
|
||||
assert!(!graph.check(&alice, &foo_read, None).await);
|
||||
assert!(!graph.check(&alice, &bar_read, None).await);
|
||||
|
||||
graph.insert(charlie, foo_read).await;
|
||||
graph.insert(charlie, bar_read).await;
|
||||
graph.insert(&charlie, &foo_read).await;
|
||||
graph.insert(&charlie, &bar_read).await;
|
||||
|
||||
assert!(graph.has_recursive(charlie, foo_read, None).await);
|
||||
assert!(graph.has_recursive(charlie, bar_read, None).await);
|
||||
assert!(graph.check(&charlie, &foo_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 Exists(ExistsReq) returns (ExistsRes);
|
||||
rpc IsPermitted(IsPermittedReq) returns (IsPermittedRes);
|
||||
rpc Expand(ExpandReq) returns (ExpandRes);
|
||||
}
|
||||
|
||||
|
||||
message GrantReq{
|
||||
Object src = 1;
|
||||
Object dst = 2;
|
||||
oneof src {
|
||||
Object src_obj = 1;
|
||||
Set src_set = 2;
|
||||
}
|
||||
Set dst = 3;
|
||||
}
|
||||
message GrantRes{}
|
||||
|
||||
message RevokeReq{
|
||||
Object src = 1;
|
||||
Object dst = 2;
|
||||
oneof src {
|
||||
Object src_obj = 1;
|
||||
Set src_set = 2;
|
||||
}
|
||||
Set dst = 3;
|
||||
}
|
||||
message RevokeRes{}
|
||||
|
||||
message ExistsReq{
|
||||
Object src = 1;
|
||||
Object dst = 2;
|
||||
oneof src {
|
||||
Object src_obj = 1;
|
||||
Set src_set = 2;
|
||||
}
|
||||
Set dst = 3;
|
||||
}
|
||||
message ExistsRes{
|
||||
bool exists = 1;
|
||||
}
|
||||
|
||||
message IsPermittedReq{
|
||||
Object src = 1;
|
||||
Object dst = 2;
|
||||
oneof src {
|
||||
Object src_obj = 1;
|
||||
Set src_set = 2;
|
||||
}
|
||||
Set dst = 3;
|
||||
}
|
||||
message IsPermittedRes{
|
||||
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{
|
||||
string namespace = 1;
|
||||
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 log::info;
|
||||
use rebacs_core::{NodeId, RelationGraph};
|
||||
use rebacs_core::{RObject, RObjectOrSet, RSet, RelationGraph};
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tonic::metadata::MetadataMap;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::rebacs_proto::Object;
|
||||
use crate::rebacs_proto::{
|
||||
rebac_service_server, ExistsReq, ExistsRes, GrantReq, GrantRes, IsPermittedReq, IsPermittedRes,
|
||||
RevokeReq, RevokeRes,
|
||||
exists_req, grant_req, is_permitted_req, rebac_service_server, revoke_req, ExistsReq,
|
||||
ExistsRes, ExpandReq, ExpandRes, ExpandResItem, GrantReq, GrantRes, IsPermittedReq,
|
||||
IsPermittedRes, Object, RevokeReq, RevokeRes, Set,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -22,37 +22,63 @@ pub struct RebacService {
|
|||
pub save_trigger: Sender<()>,
|
||||
}
|
||||
|
||||
const NAMESPACE_NS: &str = "namespace";
|
||||
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]
|
||||
impl rebac_service_server::RebacService for RebacService {
|
||||
async fn grant(&self, request: Request<GrantReq>) -> Result<Response<GrantRes>, Status> {
|
||||
let token =
|
||||
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(
|
||||
"token not permitted to grant permissions on dst",
|
||||
));
|
||||
}
|
||||
|
||||
info!(
|
||||
"created relation {}:{}#{}@{}:{}#{} for {}",
|
||||
dst.namespace,
|
||||
dst.id,
|
||||
dst.relation.clone().unwrap_or_default(),
|
||||
src.namespace,
|
||||
src.id,
|
||||
src.relation.clone().unwrap_or_default(),
|
||||
dst.namespace(),
|
||||
dst.id(),
|
||||
dst.relation(),
|
||||
src.namespace(),
|
||||
src.id(),
|
||||
src.relation().map(|x| x.to_string()).unwrap_or_default(),
|
||||
token.claims.sub
|
||||
);
|
||||
|
||||
self.graph.insert(src, dst).await;
|
||||
self.graph.insert(src, &dst).await;
|
||||
|
||||
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> {
|
||||
let token =
|
||||
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(
|
||||
"token not permitted to revoke permissions on dst",
|
||||
));
|
||||
}
|
||||
|
||||
self.graph
|
||||
.remove(
|
||||
(
|
||||
src.namespace.to_string(),
|
||||
src.id.to_string(),
|
||||
src.relation.clone(),
|
||||
),
|
||||
(dst.namespace.clone(), dst.id.clone(), dst.relation.clone()),
|
||||
)
|
||||
.await;
|
||||
self.graph.remove(&src, &dst).await;
|
||||
|
||||
info!(
|
||||
"delted relation {}:{}#{}@{}:{}#{} for {}",
|
||||
dst.namespace,
|
||||
dst.id,
|
||||
dst.relation.clone().unwrap_or_default(),
|
||||
src.namespace,
|
||||
src.id,
|
||||
src.relation.clone().unwrap_or_default(),
|
||||
dst.namespace(),
|
||||
dst.id(),
|
||||
dst.relation(),
|
||||
src.namespace(),
|
||||
src.id(),
|
||||
src.relation().map(|x| x.to_string()).unwrap_or_default(),
|
||||
token.claims.sub
|
||||
);
|
||||
|
||||
|
@ -100,9 +120,11 @@ impl rebac_service_server::RebacService for RebacService {
|
|||
let token =
|
||||
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 }))
|
||||
}
|
||||
|
@ -114,12 +136,50 @@ impl rebac_service_server::RebacService for RebacService {
|
|||
let token =
|
||||
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 }))
|
||||
}
|
||||
|
||||
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)]
|
||||
|
@ -154,54 +214,18 @@ async fn extract_token(
|
|||
Ok(token)
|
||||
}
|
||||
|
||||
async fn is_permitted(
|
||||
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();
|
||||
fn extract_dst(dst: Option<&Set>) -> Result<RSet, Status> {
|
||||
let dst = dst
|
||||
.as_ref()
|
||||
.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"));
|
||||
}
|
||||
if dst.id.is_empty() {
|
||||
if dst.id().is_empty() {
|
||||
return Err(Status::invalid_argument("dst.id must be set"));
|
||||
}
|
||||
|
||||
if src.namespace.is_empty() {
|
||||
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))
|
||||
Ok(dst)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,10 @@ use tokio::{
|
|||
use tonic::transport::Server;
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
tonic::include_proto!("eu.zettoit.rebacs");
|
Loading…
Reference in a new issue