This commit is contained in:
Paul Zinselmeyer 2023-12-08 16:05:32 +01:00
parent 890c7bc29a
commit bbf933a68b
Signed by: pfzetto
GPG key ID: 4EEF46A5B276E648
12 changed files with 199 additions and 128 deletions

28
Cargo.lock generated
View file

@ -599,9 +599,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@ -644,9 +644,9 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.8" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
@ -890,14 +890,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "rebacs_core" name = "rebacdb"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"tokio", "tokio",
] ]
[[package]] [[package]]
name = "rebacs_server" name = "rebacserver"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dotenvy", "dotenvy",
@ -905,7 +905,7 @@ dependencies = [
"jsonwebtoken", "jsonwebtoken",
"log", "log",
"prost", "prost",
"rebacs_core", "rebacdb",
"reqwest", "reqwest",
"serde", "serde",
"thiserror", "thiserror",
@ -1154,9 +1154,9 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.3" version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys",
@ -1283,9 +1283,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.32.0" version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -1293,7 +1293,7 @@ dependencies = [
"mio", "mio",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
"socket2 0.5.3", "socket2 0.5.5",
"tokio-macros", "tokio-macros",
"windows-sys", "windows-sys",
] ]
@ -1310,9 +1310,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -1,8 +1,8 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"rebacs_server", "rebacserver",
"rebacs_core", "rebacdb",
] ]
[workspace.package] [workspace.package]

38
README.md Normal file
View file

@ -0,0 +1,38 @@
This library implements a in-memory relationship-based access control dababase, that was inspired by [Google's Zanzibar](https://research.google/pubs/pub48190/).
# Naming
## `RObject`
A `RObject` is a tuple of the values (`namespace`, `id`).
It represents a object like a user.
Example: (`users`, `alice`).
## `RSet`
A `RSet` is a tuple of the values (`namespace`, `id`, `permission`).
It represents a permission for a `RObject`.
Example: (`files`, `foo.pdf`, `read`).
# Usage
The `RelationGraph`-struct contains a graph of all relationships.
Relationships can be created between:
- `RObject` and `RSet` => user alice can read the file foo.pdf.
- `RSet` and `RSet` => everyone who can read the file foo.pdf can read the file bar.pdf.
# Specials
- The `*`-id is used as a wildcard id to create a virtual relation from this id to every other id in the namespace.
Example: (`user`, `alice`) -> (`file`, `*`, `read`) => user alice can read every file
# Roadmap
- [ ] implement raft protocol to allow ha deployment
# Server
A basic gRPC based server for interacting with the database can be found in the git repository.
# Contributing
I'm happy about any contribution in any form.
Feel free to submit feature requests and bug reports using a GitHub Issue.
PR's are also appreciated.
# License
This Library is licensed under [LGPLv3](https://www.gnu.org/licenses/lgpl-3.0.en.html).

16
rebacdb/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "rebacdb"
description = "A relationship-based access control database inspired by google zanzibar."
version = "0.1.0"
edition = "2021"
authors = [ "Paul Z <info@pfz4.de>" ]
readme = "../README.md"
repository = "https://github.com/pfz4/rebacs"
license = "LGPL-3.0-or-later"
keywords = [ "accesscontrol", "relationship", "rebac", "database" ]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.34", default-features = false, features = [ "io-util", "sync" ] }

View file

@ -1,3 +1,4 @@
#![doc = include_str!("../README.md")]
use std::{ use std::{
borrow::{Borrow, Cow}, borrow::{Borrow, Cow},
cmp::Ordering, cmp::Ordering,
@ -16,32 +17,34 @@ use tokio::{
mod tests; mod tests;
const WILDCARD_ID: &str = "*"; const WILDCARD_ID: &str = "*";
const WRITE_RELATION: &str = "grant";
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct VertexId { struct VertexId {
namespace: String, namespace: String,
id: String, id: String,
relation: Option<String>, relation: Option<String>,
} }
/// shared version of [`VertexId`]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct VertexIdRef<'a> { struct VertexIdRef<'a> {
namespace: &'a str, namespace: &'a str,
id: &'a str, id: &'a str,
relation: Option<&'a str>, relation: Option<&'a str>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum RObjectOrSet<'a> { pub enum ObjectOrSet<'a> {
Object(Cow<'a, RObject>), Object(Cow<'a, Object>),
Set(Cow<'a, RSet>), Set(Cow<'a, Set>),
} }
/// representation of a an object and a relation (e.g. (`file`, `foo.pdf`, `read`))
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct RSet(VertexId); pub struct Set(VertexId);
/// representation of an object (e.g. (`user`, `alice`))
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct RObject(VertexId); pub struct Object(VertexId);
struct Vertex { struct Vertex {
id: VertexId, id: VertexId,
@ -49,8 +52,10 @@ struct Vertex {
edges_out: RwLock<HashSet<Arc<Vertex>>>, edges_out: RwLock<HashSet<Arc<Vertex>>>,
} }
/// graph-based database implementation
#[derive(Default)] #[derive(Default)]
pub struct RelationGraph { pub struct RelationGraph {
/// all verticies of the graph
verticies: RwLock<BTreeSet<Arc<Vertex>>>, verticies: RwLock<BTreeSet<Arc<Vertex>>>,
} }
@ -61,7 +66,15 @@ trait VertexIdentifier {
fn vertex_id(&self) -> &VertexId; fn vertex_id(&self) -> &VertexId;
} }
impl RObject { impl Object {
pub fn new(namespace: String, id: String) -> Self {
Self(VertexId {
namespace,
id,
relation: None,
})
}
pub fn namespace(&self) -> &str { pub fn namespace(&self) -> &str {
&self.0.namespace &self.0.namespace
} }
@ -70,15 +83,20 @@ impl RObject {
&self.0.id &self.0.id
} }
pub fn relation(&self) -> Option<&str> {
None
}
fn vertex_id(&self) -> &VertexId { fn vertex_id(&self) -> &VertexId {
&self.0 &self.0
} }
} }
impl RSet { impl Set {
pub fn new(namespace: String, id: String, relation: String) -> Self {
Self(VertexId {
namespace,
id,
relation: Some(relation),
})
}
pub fn namespace(&self) -> &str { pub fn namespace(&self) -> &str {
&self.0.namespace &self.0.namespace
} }
@ -96,7 +114,7 @@ impl RSet {
} }
} }
impl<'a> RObjectOrSet<'a> { impl<'a> ObjectOrSet<'a> {
pub fn namespace(&self) -> &str { pub fn namespace(&self) -> &str {
match self { match self {
Self::Object(obj) => obj.namespace(), Self::Object(obj) => obj.namespace(),
@ -126,8 +144,9 @@ impl<'a> RObjectOrSet<'a> {
} }
impl RelationGraph { impl RelationGraph {
pub async fn insert(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) { /// create a new relation between from a [`RObject`] or [`RSet`] to a [`RSet`]
let src: RObjectOrSet<'_> = src.into(); pub async fn insert(&self, src: impl Into<ObjectOrSet<'_>>, dst: &Set) {
let src: ObjectOrSet<'_> = src.into();
let mut verticies = self.verticies.write().await; let mut verticies = self.verticies.write().await;
let mut get_or_create = |vertex: &VertexId| match verticies.get(vertex) { let mut get_or_create = |vertex: &VertexId| match verticies.get(vertex) {
@ -145,11 +164,11 @@ impl RelationGraph {
let src_without_relation = src.relation().is_none(); let src_without_relation = src.relation().is_none();
let src_wildcard: RObjectOrSet = (src.namespace(), WILDCARD_ID, src.relation()).into(); let src_wildcard: ObjectOrSet = (src.namespace(), WILDCARD_ID, src.relation()).into();
let src_wildcard = get_or_create(src_wildcard.vertex_id()); let src_wildcard = get_or_create(src_wildcard.vertex_id());
let src_vertex = get_or_create(src.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: Set = (dst.namespace(), WILDCARD_ID, dst.relation()).into();
let dst_wildcard = get_or_create(dst_wildcard.vertex_id()); let dst_wildcard = get_or_create(dst_wildcard.vertex_id());
let dst_vertex = get_or_create(dst.vertex_id()); let dst_vertex = get_or_create(dst.vertex_id());
@ -163,8 +182,9 @@ impl RelationGraph {
add_edge(src_vertex, dst_vertex).await; add_edge(src_vertex, dst_vertex).await;
} }
pub async fn remove(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) { /// remove a relation
let src: RObjectOrSet<'_> = src.into(); pub async fn remove(&self, src: impl Into<ObjectOrSet<'_>>, dst: &Set) {
let src: ObjectOrSet<'_> = src.into();
let mut verticies = self.verticies.write().await; let mut verticies = self.verticies.write().await;
let src = verticies.get(src.vertex_id()).cloned(); let src = verticies.get(src.vertex_id()).cloned();
@ -183,9 +203,9 @@ impl RelationGraph {
} }
} }
/// does a edge from src to dst exist /// checks if there is a *direct* relation between `src` and `dst`
pub async fn has(&self, src: impl Into<RObjectOrSet<'_>>, dst: &RSet) -> bool { pub async fn has(&self, src: impl Into<ObjectOrSet<'_>>, dst: &Set) -> bool {
let src: RObjectOrSet<'_> = src.into(); let src: ObjectOrSet<'_> = src.into();
let (src, dst) = { let (src, dst) = {
let verticies = self.verticies.read().await; let verticies = self.verticies.read().await;
( (
@ -201,14 +221,19 @@ 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](https://en.wikipedia.org/wiki/Breadth-first_search)
///
/// # Arguments
/// * `src` - start of the path
/// * `dst` - end of the path
/// * `limit` - optional maximum search depth of the search before returing false
pub async fn check<'a>( pub async fn check<'a>(
&self, &self,
src: impl Into<RObjectOrSet<'_>>, src: impl Into<ObjectOrSet<'_>>,
dst: &RSet, dst: &Set,
limit: Option<u32>, limit: Option<u32>,
) -> bool { ) -> bool {
let src: RObjectOrSet<'_> = src.into(); let src: ObjectOrSet<'_> = src.into();
let mut distance = 1; let mut distance = 1;
let mut neighbors: Vec<Arc<Vertex>> = if let Some(src) = let mut neighbors: Vec<Arc<Vertex>> = if let Some(src) =
@ -216,7 +241,7 @@ impl RelationGraph {
{ {
src.edges_out.read().await.iter().cloned().collect() src.edges_out.read().await.iter().cloned().collect()
} else { } else {
let wildcard_src: RObject = (src.namespace(), WILDCARD_ID).into(); let wildcard_src: Object = (src.namespace(), WILDCARD_ID).into();
if let Some(wildcard_src) = self.verticies.read().await.get(wildcard_src.vertex_id()) { if let Some(wildcard_src) = self.verticies.read().await.get(wildcard_src.vertex_id()) {
wildcard_src wildcard_src
.edges_out .edges_out
@ -268,25 +293,14 @@ impl RelationGraph {
false false
} }
pub async fn can_write( /// get all objects that are related to dst with the relation path
&self, pub async fn expand(&self, dst: &Set) -> Vec<(Object, Vec<Set>)> {
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 start_vertex = {
let verticies = self.verticies.read().await; let verticies = self.verticies.read().await;
match verticies.get(dst.vertex_id()) { match verticies.get(dst.vertex_id()) {
Some(v) => v.clone(), Some(v) => v.clone(),
None => { None => {
let wildcard_dst: RSet = (dst.namespace(), WILDCARD_ID, dst.relation()).into(); let wildcard_dst: Set = (dst.namespace(), WILDCARD_ID, dst.relation()).into();
match verticies.get(wildcard_dst.vertex_id()) { match verticies.get(wildcard_dst.vertex_id()) {
Some(v) => v.clone(), Some(v) => v.clone(),
@ -343,13 +357,14 @@ impl RelationGraph {
.into_iter() .into_iter()
.map(|(v, path)| { .map(|(v, path)| {
( (
RObject(v.id.clone()), Object(v.id.clone()),
path.into_iter().map(|w| RSet(w.id.clone())).collect(), path.into_iter().map(|w| Set(w.id.clone())).collect(),
) )
}) })
.collect() .collect()
} }
/// write graph to file
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 vertex in self.verticies.read().await.iter() { for vertex in self.verticies.read().await.iter() {
@ -392,6 +407,7 @@ impl RelationGraph {
} }
} }
} }
/// read graph from file
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();
@ -412,7 +428,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: RObjectOrSet = if obj.contains('#') { let src: ObjectOrSet = 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();
@ -424,7 +440,7 @@ impl RelationGraph {
let rel = &obj[sep_2 + 1..]; let rel = &obj[sep_2 + 1..];
RObjectOrSet::Set(Cow::Owned((namespace, id, rel).into())) ObjectOrSet::Set(Cow::Owned((namespace, id, rel).into()))
} else { } else {
let sep_1 = obj.find(':'); let sep_1 = obj.find(':');
@ -433,7 +449,7 @@ impl RelationGraph {
} else { } else {
(dst.0.as_str(), dst.1.as_str()) (dst.0.as_str(), dst.1.as_str())
}; };
RObjectOrSet::Object(Cow::Owned((namespace, id).into())) ObjectOrSet::Object(Cow::Owned((namespace, id).into()))
}; };
graph graph
@ -492,7 +508,7 @@ impl Hash for Vertex {
} }
} }
impl From<(&str, &str)> for RObject { impl From<(&str, &str)> for Object {
fn from(value: (&str, &str)) -> Self { fn from(value: (&str, &str)) -> Self {
Self(VertexId { Self(VertexId {
namespace: value.0.to_string(), namespace: value.0.to_string(),
@ -502,7 +518,7 @@ impl From<(&str, &str)> for RObject {
} }
} }
impl From<(String, String)> for RObject { impl From<(String, String)> for Object {
fn from(value: (String, String)) -> Self { fn from(value: (String, String)) -> Self {
Self(VertexId { Self(VertexId {
namespace: value.0, namespace: value.0,
@ -512,7 +528,7 @@ impl From<(String, String)> for RObject {
} }
} }
impl From<(&str, &str, &str)> for RSet { impl From<(&str, &str, &str)> for Set {
fn from(value: (&str, &str, &str)) -> Self { fn from(value: (&str, &str, &str)) -> Self {
Self(VertexId { Self(VertexId {
namespace: value.0.to_string(), namespace: value.0.to_string(),
@ -522,7 +538,7 @@ impl From<(&str, &str, &str)> for RSet {
} }
} }
impl From<(String, String, String)> for RSet { impl From<(String, String, String)> for Set {
fn from(value: (String, String, String)) -> Self { fn from(value: (String, String, String)) -> Self {
Self(VertexId { Self(VertexId {
namespace: value.0, namespace: value.0,
@ -532,7 +548,7 @@ impl From<(String, String, String)> for RSet {
} }
} }
impl From<(&str, &str, Option<&str>)> for RObjectOrSet<'_> { impl From<(&str, &str, Option<&str>)> for ObjectOrSet<'_> {
fn from(value: (&str, &str, Option<&str>)) -> Self { fn from(value: (&str, &str, Option<&str>)) -> Self {
match value.2 { match value.2 {
Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())), Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())),
@ -541,7 +557,7 @@ impl From<(&str, &str, Option<&str>)> for RObjectOrSet<'_> {
} }
} }
impl From<(String, String, Option<String>)> for RObjectOrSet<'_> { impl From<(String, String, Option<String>)> for ObjectOrSet<'_> {
fn from(value: (String, String, Option<String>)) -> Self { fn from(value: (String, String, Option<String>)) -> Self {
match value.2 { match value.2 {
Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())), Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())),
@ -550,53 +566,53 @@ impl From<(String, String, Option<String>)> for RObjectOrSet<'_> {
} }
} }
impl Borrow<VertexId> for RSet { impl Borrow<VertexId> for Set {
fn borrow(&self) -> &VertexId { fn borrow(&self) -> &VertexId {
&self.0 &self.0
} }
} }
impl PartialEq<VertexId> for RSet { impl PartialEq<VertexId> for Set {
fn eq(&self, other: &VertexId) -> bool { fn eq(&self, other: &VertexId) -> bool {
self.0.eq(other) self.0.eq(other)
} }
} }
impl PartialEq<RSet> for VertexId { impl PartialEq<Set> for VertexId {
fn eq(&self, other: &RSet) -> bool { fn eq(&self, other: &Set) -> bool {
self.eq(&other.0) self.eq(&other.0)
} }
} }
impl Borrow<VertexId> for RObject { impl Borrow<VertexId> for Object {
fn borrow(&self) -> &VertexId { fn borrow(&self) -> &VertexId {
&self.0 &self.0
} }
} }
impl From<RSet> for RObjectOrSet<'_> { impl From<Set> for ObjectOrSet<'_> {
fn from(value: RSet) -> Self { fn from(value: Set) -> Self {
Self::Set(Cow::Owned(value)) Self::Set(Cow::Owned(value))
} }
} }
impl From<RObject> for RObjectOrSet<'_> { impl From<Object> for ObjectOrSet<'_> {
fn from(value: RObject) -> Self { fn from(value: Object) -> Self {
Self::Object(Cow::Owned(value)) Self::Object(Cow::Owned(value))
} }
} }
impl<'a> From<&'a RSet> for RObjectOrSet<'a> { impl<'a> From<&'a Set> for ObjectOrSet<'a> {
fn from(value: &'a RSet) -> Self { fn from(value: &'a Set) -> Self {
Self::Set(Cow::Borrowed(value)) Self::Set(Cow::Borrowed(value))
} }
} }
impl<'a> From<&'a RObject> for RObjectOrSet<'a> { impl<'a> From<&'a Object> for ObjectOrSet<'a> {
fn from(value: &'a RObject) -> Self { fn from(value: &'a Object) -> Self {
Self::Object(Cow::Borrowed(value)) Self::Object(Cow::Borrowed(value))
} }
} }
impl<'a> From<&'a RObjectOrSet<'a>> for RObjectOrSet<'a> { impl<'a> From<&'a ObjectOrSet<'a>> for ObjectOrSet<'a> {
fn from(value: &'a RObjectOrSet<'a>) -> Self { fn from(value: &'a ObjectOrSet<'a>) -> Self {
match value { match value {
Self::Object(obj) => Self::Object(Cow::Borrowed(obj.borrow())), Self::Object(obj) => Self::Object(Cow::Borrowed(obj.borrow())),
Self::Set(set) => Self::Set(Cow::Borrowed(set.borrow())), Self::Set(set) => Self::Set(Cow::Borrowed(set.borrow())),

View file

@ -1,15 +1,15 @@
use crate::{RObject, RSet, RelationGraph, WILDCARD_ID}; use crate::{Object, RelationGraph, Set, WILDCARD_ID};
#[tokio::test] #[tokio::test]
async fn simple_graph() { async fn simple_graph() {
let graph = RelationGraph::default(); let graph = RelationGraph::default();
let alice: RObject = ("user", "alice").into(); let alice: Object = ("user", "alice").into();
let bob: RObject = ("user", "bob").into(); let bob: Object = ("user", "bob").into();
let charlie: RObject = ("user", "charlie").into(); let charlie: Object = ("user", "charlie").into();
let foo_read: RSet = ("application", "foo", "read").into(); let foo_read: Set = ("application", "foo", "read").into();
let bar_read: RSet = ("application", "bar", "read").into(); let bar_read: Set = ("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;
@ -40,18 +40,18 @@ async fn simple_graph() {
async fn wildcard() { async fn wildcard() {
let graph = RelationGraph::default(); let graph = RelationGraph::default();
let alice: RObject = ("user", "alice").into(); let alice: Object = ("user", "alice").into();
let bob: RObject = ("user", "bob").into(); let bob: Object = ("user", "bob").into();
let charlie: RObject = ("user", "charlie").into(); let charlie: Object = ("user", "charlie").into();
let user_wildcard: RObject = ("user", WILDCARD_ID).into(); let user_wildcard: Object = ("user", WILDCARD_ID).into();
let foo_read: RSet = ("application", "foo", "read").into(); let foo_read: Set = ("application", "foo", "read").into();
let bar_read: RSet = ("application", "bar", "read").into(); let bar_read: Set = ("application", "bar", "read").into();
let app_read: RSet = ("application", WILDCARD_ID, "read").into(); let app_read: Set = ("application", WILDCARD_ID, "read").into();
let some_app_read: RSet = ("application", "bla", "read").into(); let some_app_read: Set = ("application", "bla", "read").into();
graph.insert(&alice, &foo_read).await; graph.insert(&alice, &foo_read).await;
graph.insert(&user_wildcard, &foo_read).await; graph.insert(&user_wildcard, &foo_read).await;

View file

@ -1,9 +0,0 @@
[package]
name = "rebacs_core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.32.0", features = [] }

View file

@ -1,7 +1,8 @@
[package] [package]
name = "rebacs_server" name = "rebacserver"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -22,7 +23,7 @@ jsonwebtoken = "8.3.0"
reqwest = { version="0.11.20", features=["json", "rustls-tls"], default-features=false} reqwest = { version="0.11.20", features=["json", "rustls-tls"], default-features=false}
rebacs_core = { path="../rebacs_core" } rebacdb = { path="../rebacdb" }
[build-dependencies] [build-dependencies]
tonic-build = "0.9.2" tonic-build = "0.9.2"

View file

@ -2,7 +2,7 @@ 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::{RObject, RObjectOrSet, RSet, RelationGraph}; use rebacdb::{Object as DbObject, ObjectOrSet, RelationGraph, Set as DbSet};
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use tonic::metadata::MetadataMap; use tonic::metadata::MetadataMap;
@ -29,12 +29,12 @@ 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into();
let src = extract_src(request.get_ref().src.clone(), &user)?; let src = extract_src(request.get_ref().src.clone(), &user)?;
let dst = extract_dst(request.get_ref().dst.clone())?; let dst = extract_dst(request.get_ref().dst.clone())?;
if !self.graph.can_write(&user, &dst, None).await { if !crate::can_write(&self.graph, &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",
)); ));
@ -59,12 +59,12 @@ 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into();
let src = extract_src(request.get_ref().src.clone(), &user)?; let src = extract_src(request.get_ref().src.clone(), &user)?;
let dst = extract_dst(request.get_ref().dst.clone())?; let dst = extract_dst(request.get_ref().dst.clone())?;
if !self.graph.can_write(&user, &dst, None).await { if !crate::can_write(&self.graph, &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",
)); ));
@ -90,7 +90,7 @@ impl rebac_service_server::RebacService for RebacService {
async fn exists(&self, request: Request<ExistsReq>) -> Result<Response<ExistsRes>, Status> { async fn exists(&self, request: Request<ExistsReq>) -> Result<Response<ExistsRes>, 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into();
let src = extract_src(request.get_ref().src.clone(), &user)?; let src = extract_src(request.get_ref().src.clone(), &user)?;
let dst = extract_dst(request.get_ref().dst.clone())?; let dst = extract_dst(request.get_ref().dst.clone())?;
@ -106,7 +106,7 @@ impl rebac_service_server::RebacService for RebacService {
) -> Result<Response<IsPermittedRes>, Status> { ) -> Result<Response<IsPermittedRes>, 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into();
let src = extract_src(request.get_ref().src.clone(), &user)?; let src = extract_src(request.get_ref().src.clone(), &user)?;
let dst = extract_dst(request.get_ref().dst.clone())?; let dst = extract_dst(request.get_ref().dst.clone())?;
@ -121,8 +121,8 @@ impl rebac_service_server::RebacService for RebacService {
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?; extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
let dst = extract_dst(request.get_ref().dst.clone())?; let dst = extract_dst(request.get_ref().dst.clone())?;
let user: RObject = (USER_NS, token.claims.sub.as_str()).into(); let user: DbObject = (USER_NS, token.claims.sub.as_str()).into();
if !self.graph.can_write(&user, &dst, None).await { if !crate::can_write(&self.graph, &user, &dst, None).await {
return Err(Status::permission_denied( return Err(Status::permission_denied(
"token not permitted to expand permissions on dst", "token not permitted to expand permissions on dst",
)); ));
@ -186,11 +186,11 @@ async fn extract_token(
} }
fn extract_src<'a>( fn extract_src<'a>(
src: Option<impl Into<RObjectOrSet<'a>>>, src: Option<impl Into<ObjectOrSet<'a>>>,
fallback_user: &'a RObject, fallback_user: &'a DbObject,
) -> Result<RObjectOrSet<'a>, Status> { ) -> Result<ObjectOrSet<'a>, Status> {
if let Some(src) = src { if let Some(src) = src {
let src: RObjectOrSet<'_> = src.into(); let src: ObjectOrSet<'_> = src.into();
if src.namespace().is_empty() { if src.namespace().is_empty() {
Err(Status::invalid_argument("src.namespace must be set")) Err(Status::invalid_argument("src.namespace must be set"))
} else if src.id().is_empty() { } else if src.id().is_empty() {
@ -203,9 +203,9 @@ fn extract_src<'a>(
} }
} }
fn extract_dst(dst: Option<Set>) -> Result<RSet, Status> { fn extract_dst(dst: Option<Set>) -> Result<DbSet, Status> {
let dst = dst.ok_or(Status::invalid_argument("dst must be set"))?; let dst = dst.ok_or(Status::invalid_argument("dst must be set"))?;
let dst: RSet = (dst.namespace, dst.id, dst.relation).into(); let dst: DbSet = (dst.namespace, dst.id, dst.relation).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"));
@ -219,7 +219,7 @@ fn extract_dst(dst: Option<Set>) -> Result<RSet, Status> {
macro_rules! from_src { macro_rules! from_src {
($src:path) => { ($src:path) => {
impl From<$src> for RObjectOrSet<'_> { impl From<$src> for ObjectOrSet<'_> {
fn from(value: $src) -> Self { fn from(value: $src) -> Self {
use $src; use $src;
match value { match value {

View file

@ -1,11 +1,9 @@
#![feature(btree_cursors)]
use std::{env, sync::Arc, time::Duration}; use std::{env, sync::Arc, time::Duration};
use grpc_service::RebacService; use grpc_service::RebacService;
use jsonwebtoken::{Algorithm, DecodingKey, Validation}; use jsonwebtoken::{Algorithm, DecodingKey, Validation};
use log::info; use log::info;
use rebacs_core::RelationGraph; use rebacdb::{ObjectOrSet, RelationGraph, Set};
use serde::Deserialize; use serde::Deserialize;
use tokio::{ use tokio::{
fs::{self, File}, fs::{self, File},
@ -96,3 +94,14 @@ async fn main() {
.await .await
.unwrap() .unwrap()
} }
pub async fn can_write(
graph: &RelationGraph,
src: impl Into<ObjectOrSet<'_>>,
dst: &Set,
limit: Option<u32>,
) -> bool {
graph
.check(src, &(dst.namespace(), dst.id(), "grant").into(), limit)
.await
}