major rework

This commit is contained in:
Paul Zinselmeyer 2023-09-06 22:41:42 +02:00
parent 92090ffc3d
commit a39abdacb0
Signed by: pfzetto
GPG key ID: 4EEF46A5B276E648
6 changed files with 596 additions and 268 deletions

View file

@ -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", &current.0, &current.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())),
}
}
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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)
}

View file

@ -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;

View file

@ -1 +0,0 @@
tonic::include_proto!("eu.zettoit.rebacs");