diff --git a/Cargo.lock b/Cargo.lock index 98db67b..3c96e19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,9 +599,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -890,14 +890,14 @@ dependencies = [ ] [[package]] -name = "rebacs_core" +name = "rebacdb" version = "0.1.0" dependencies = [ "tokio", ] [[package]] -name = "rebacs_server" +name = "rebacserver" version = "0.1.0" dependencies = [ "dotenvy", @@ -905,7 +905,7 @@ dependencies = [ "jsonwebtoken", "log", "prost", - "rebacs_core", + "rebacdb", "reqwest", "serde", "thiserror", @@ -1154,9 +1154,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -1283,9 +1283,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -1293,7 +1293,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] @@ -1310,9 +1310,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9429ceb..4983aa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] resolver = "2" members = [ - "rebacs_server", - "rebacs_core", + "rebacserver", + "rebacdb", ] [workspace.package] diff --git a/README.md b/README.md new file mode 100644 index 0000000..733be87 --- /dev/null +++ b/README.md @@ -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). diff --git a/rebacdb/Cargo.toml b/rebacdb/Cargo.toml new file mode 100644 index 0000000..2493770 --- /dev/null +++ b/rebacdb/Cargo.toml @@ -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 " ] +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" ] } diff --git a/rebacs_core/src/lib.rs b/rebacdb/src/lib.rs similarity index 80% rename from rebacs_core/src/lib.rs rename to rebacdb/src/lib.rs index 3f3beff..c314588 100644 --- a/rebacs_core/src/lib.rs +++ b/rebacdb/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] use std::{ borrow::{Borrow, Cow}, cmp::Ordering, @@ -16,32 +17,34 @@ use tokio::{ mod tests; const WILDCARD_ID: &str = "*"; -const WRITE_RELATION: &str = "grant"; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub struct VertexId { +struct VertexId { namespace: String, id: String, relation: Option, } +/// shared version of [`VertexId`] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub struct VertexIdRef<'a> { +struct VertexIdRef<'a> { namespace: &'a str, id: &'a str, relation: Option<&'a str>, } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum RObjectOrSet<'a> { - Object(Cow<'a, RObject>), - Set(Cow<'a, RSet>), +pub enum ObjectOrSet<'a> { + Object(Cow<'a, Object>), + Set(Cow<'a, Set>), } +/// representation of a an object and a relation (e.g. (`file`, `foo.pdf`, `read`)) #[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)] -pub struct RObject(VertexId); +pub struct Object(VertexId); struct Vertex { id: VertexId, @@ -49,8 +52,10 @@ struct Vertex { edges_out: RwLock>>, } +/// graph-based database implementation #[derive(Default)] pub struct RelationGraph { + /// all verticies of the graph verticies: RwLock>>, } @@ -61,7 +66,15 @@ trait VertexIdentifier { 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 { &self.0.namespace } @@ -70,15 +83,20 @@ impl RObject { &self.0.id } - pub fn relation(&self) -> Option<&str> { - None - } fn vertex_id(&self) -> &VertexId { &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 { &self.0.namespace } @@ -96,7 +114,7 @@ impl RSet { } } -impl<'a> RObjectOrSet<'a> { +impl<'a> ObjectOrSet<'a> { pub fn namespace(&self) -> &str { match self { Self::Object(obj) => obj.namespace(), @@ -126,8 +144,9 @@ impl<'a> RObjectOrSet<'a> { } impl RelationGraph { - pub async fn insert(&self, src: impl Into>, dst: &RSet) { - let src: RObjectOrSet<'_> = src.into(); + /// create a new relation between from a [`RObject`] or [`RSet`] to a [`RSet`] + pub async fn insert(&self, src: impl Into>, dst: &Set) { + let src: ObjectOrSet<'_> = src.into(); let mut verticies = self.verticies.write().await; 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_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_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_vertex = get_or_create(dst.vertex_id()); @@ -163,8 +182,9 @@ impl RelationGraph { add_edge(src_vertex, dst_vertex).await; } - pub async fn remove(&self, src: impl Into>, dst: &RSet) { - let src: RObjectOrSet<'_> = src.into(); + /// remove a relation + pub async fn remove(&self, src: impl Into>, dst: &Set) { + let src: ObjectOrSet<'_> = src.into(); let mut verticies = self.verticies.write().await; let src = verticies.get(src.vertex_id()).cloned(); @@ -183,9 +203,9 @@ impl RelationGraph { } } - /// does a edge from src to dst exist - pub async fn has(&self, src: impl Into>, dst: &RSet) -> bool { - let src: RObjectOrSet<'_> = src.into(); + /// checks if there is a *direct* relation between `src` and `dst` + pub async fn has(&self, src: impl Into>, dst: &Set) -> bool { + let src: ObjectOrSet<'_> = src.into(); let (src, dst) = { 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>( &self, - src: impl Into>, - dst: &RSet, + src: impl Into>, + dst: &Set, limit: Option, ) -> bool { - let src: RObjectOrSet<'_> = src.into(); + let src: ObjectOrSet<'_> = src.into(); let mut distance = 1; let mut neighbors: Vec> = if let Some(src) = @@ -216,7 +241,7 @@ impl RelationGraph { { src.edges_out.read().await.iter().cloned().collect() } 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()) { wildcard_src .edges_out @@ -268,25 +293,14 @@ impl RelationGraph { false } - pub async fn can_write( - &self, - src: impl Into>, - dst: &RSet, - limit: Option, - ) -> 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)> { + /// get all objects that are related to dst with the relation path + pub async fn expand(&self, dst: &Set) -> Vec<(Object, Vec)> { 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(); + let wildcard_dst: Set = (dst.namespace(), WILDCARD_ID, dst.relation()).into(); match verticies.get(wildcard_dst.vertex_id()) { Some(v) => v.clone(), @@ -343,13 +357,14 @@ impl RelationGraph { .into_iter() .map(|(v, path)| { ( - RObject(v.id.clone()), - path.into_iter().map(|w| RSet(w.id.clone())).collect(), + Object(v.id.clone()), + path.into_iter().map(|w| Set(w.id.clone())).collect(), ) }) .collect() } + /// write graph to file pub async fn write_savefile(&self, writeable: &mut (impl AsyncWriteExt + Unpin)) { let mut current: (String, String) = (String::new(), String::new()); 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 { let mut lines = readable.lines(); let graph = Self::default(); @@ -412,7 +428,7 @@ impl RelationGraph { let arr = line[arr_start + 1..arr_stop].trim().split(", "); for obj in arr { - let src: RObjectOrSet = if obj.contains('#') { + let src: ObjectOrSet = if obj.contains('#') { let sep_1 = obj.find(':'); let sep_2 = obj.find('#').unwrap(); @@ -424,7 +440,7 @@ impl RelationGraph { let rel = &obj[sep_2 + 1..]; - RObjectOrSet::Set(Cow::Owned((namespace, id, rel).into())) + ObjectOrSet::Set(Cow::Owned((namespace, id, rel).into())) } else { let sep_1 = obj.find(':'); @@ -433,7 +449,7 @@ impl RelationGraph { } else { (dst.0.as_str(), dst.1.as_str()) }; - RObjectOrSet::Object(Cow::Owned((namespace, id).into())) + ObjectOrSet::Object(Cow::Owned((namespace, id).into())) }; 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 { Self(VertexId { 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 { Self(VertexId { 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 { Self(VertexId { 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 { Self(VertexId { 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 { match value.2 { 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)> for RObjectOrSet<'_> { +impl From<(String, String, Option)> for ObjectOrSet<'_> { fn from(value: (String, String, Option)) -> Self { match value.2 { Some(r) => Self::Set(Cow::Owned((value.0, value.1, r).into())), @@ -550,53 +566,53 @@ impl From<(String, String, Option)> for RObjectOrSet<'_> { } } -impl Borrow for RSet { +impl Borrow for Set { fn borrow(&self) -> &VertexId { &self.0 } } -impl PartialEq for RSet { +impl PartialEq for Set { fn eq(&self, other: &VertexId) -> bool { self.0.eq(other) } } -impl PartialEq for VertexId { - fn eq(&self, other: &RSet) -> bool { +impl PartialEq for VertexId { + fn eq(&self, other: &Set) -> bool { self.eq(&other.0) } } -impl Borrow for RObject { +impl Borrow for Object { fn borrow(&self) -> &VertexId { &self.0 } } -impl From for RObjectOrSet<'_> { - fn from(value: RSet) -> Self { +impl From for ObjectOrSet<'_> { + fn from(value: Set) -> Self { Self::Set(Cow::Owned(value)) } } -impl From for RObjectOrSet<'_> { - fn from(value: RObject) -> Self { +impl From for ObjectOrSet<'_> { + fn from(value: Object) -> Self { Self::Object(Cow::Owned(value)) } } -impl<'a> From<&'a RSet> for RObjectOrSet<'a> { - fn from(value: &'a RSet) -> Self { +impl<'a> From<&'a Set> for ObjectOrSet<'a> { + fn from(value: &'a Set) -> Self { Self::Set(Cow::Borrowed(value)) } } -impl<'a> From<&'a RObject> for RObjectOrSet<'a> { - fn from(value: &'a RObject) -> Self { +impl<'a> From<&'a Object> for ObjectOrSet<'a> { + fn from(value: &'a Object) -> Self { Self::Object(Cow::Borrowed(value)) } } -impl<'a> From<&'a RObjectOrSet<'a>> for RObjectOrSet<'a> { - fn from(value: &'a RObjectOrSet<'a>) -> Self { +impl<'a> From<&'a ObjectOrSet<'a>> for ObjectOrSet<'a> { + fn from(value: &'a ObjectOrSet<'a>) -> Self { match value { Self::Object(obj) => Self::Object(Cow::Borrowed(obj.borrow())), Self::Set(set) => Self::Set(Cow::Borrowed(set.borrow())), diff --git a/rebacs_core/src/tests.rs b/rebacdb/src/tests.rs similarity index 67% rename from rebacs_core/src/tests.rs rename to rebacdb/src/tests.rs index eadfed9..df20a66 100644 --- a/rebacs_core/src/tests.rs +++ b/rebacdb/src/tests.rs @@ -1,15 +1,15 @@ -use crate::{RObject, RSet, RelationGraph, WILDCARD_ID}; +use crate::{Object, RelationGraph, Set, WILDCARD_ID}; #[tokio::test] async fn simple_graph() { let graph = RelationGraph::default(); - let alice: RObject = ("user", "alice").into(); - let bob: RObject = ("user", "bob").into(); - let charlie: RObject = ("user", "charlie").into(); + let alice: Object = ("user", "alice").into(); + let bob: Object = ("user", "bob").into(); + let charlie: Object = ("user", "charlie").into(); - let foo_read: RSet = ("application", "foo", "read").into(); - let bar_read: RSet = ("application", "bar", "read").into(); + let foo_read: Set = ("application", "foo", "read").into(); + let bar_read: Set = ("application", "bar", "read").into(); graph.insert(&alice, &foo_read).await; graph.insert(&bob, &bar_read).await; @@ -40,18 +40,18 @@ async fn simple_graph() { 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 alice: Object = ("user", "alice").into(); + let bob: Object = ("user", "bob").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 bar_read: RSet = ("application", "bar", "read").into(); + let foo_read: Set = ("application", "foo", "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(&user_wildcard, &foo_read).await; diff --git a/rebacs_core/Cargo.toml b/rebacs_core/Cargo.toml deleted file mode 100644 index c192439..0000000 --- a/rebacs_core/Cargo.toml +++ /dev/null @@ -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 = [] } diff --git a/rebacs_server/Cargo.toml b/rebacserver/Cargo.toml similarity index 89% rename from rebacs_server/Cargo.toml rename to rebacserver/Cargo.toml index 6bb7646..94b267e 100644 --- a/rebacs_server/Cargo.toml +++ b/rebacserver/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "rebacs_server" +name = "rebacserver" version = "0.1.0" edition = "2021" +publish = false # 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} -rebacs_core = { path="../rebacs_core" } +rebacdb = { path="../rebacdb" } [build-dependencies] tonic-build = "0.9.2" diff --git a/rebacs_server/build.rs b/rebacserver/build.rs similarity index 100% rename from rebacs_server/build.rs rename to rebacserver/build.rs diff --git a/rebacs_server/proto/rebacs.proto b/rebacserver/proto/rebacs.proto similarity index 100% rename from rebacs_server/proto/rebacs.proto rename to rebacserver/proto/rebacs.proto diff --git a/rebacs_server/src/grpc_service.rs b/rebacserver/src/grpc_service.rs similarity index 87% rename from rebacs_server/src/grpc_service.rs rename to rebacserver/src/grpc_service.rs index d463385..6416755 100644 --- a/rebacs_server/src/grpc_service.rs +++ b/rebacserver/src/grpc_service.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use jsonwebtoken::{decode, DecodingKey, TokenData, Validation}; use log::info; -use rebacs_core::{RObject, RObjectOrSet, RSet, RelationGraph}; +use rebacdb::{Object as DbObject, ObjectOrSet, RelationGraph, Set as DbSet}; use serde::Deserialize; use tokio::sync::mpsc::Sender; use tonic::metadata::MetadataMap; @@ -29,12 +29,12 @@ impl rebac_service_server::RebacService for RebacService { async fn grant(&self, request: Request) -> Result, 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into(); let src = extract_src(request.get_ref().src.clone(), &user)?; 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( "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) -> Result, 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into(); let src = extract_src(request.get_ref().src.clone(), &user)?; 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( "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) -> Result, 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into(); let src = extract_src(request.get_ref().src.clone(), &user)?; let dst = extract_dst(request.get_ref().dst.clone())?; @@ -106,7 +106,7 @@ impl rebac_service_server::RebacService for RebacService { ) -> Result, 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 user: DbObject = (USER_NS, token.claims.sub.as_str()).into(); let src = extract_src(request.get_ref().src.clone(), &user)?; 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?; let dst = extract_dst(request.get_ref().dst.clone())?; - let user: RObject = (USER_NS, token.claims.sub.as_str()).into(); - if !self.graph.can_write(&user, &dst, None).await { + let user: DbObject = (USER_NS, token.claims.sub.as_str()).into(); + if !crate::can_write(&self.graph, &user, &dst, None).await { return Err(Status::permission_denied( "token not permitted to expand permissions on dst", )); @@ -186,11 +186,11 @@ async fn extract_token( } fn extract_src<'a>( - src: Option>>, - fallback_user: &'a RObject, -) -> Result, Status> { + src: Option>>, + fallback_user: &'a DbObject, +) -> Result, Status> { if let Some(src) = src { - let src: RObjectOrSet<'_> = src.into(); + let src: ObjectOrSet<'_> = src.into(); if src.namespace().is_empty() { Err(Status::invalid_argument("src.namespace must be set")) } else if src.id().is_empty() { @@ -203,9 +203,9 @@ fn extract_src<'a>( } } -fn extract_dst(dst: Option) -> Result { +fn extract_dst(dst: Option) -> Result { 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() { return Err(Status::invalid_argument("dst.namespace must be set")); @@ -219,7 +219,7 @@ fn extract_dst(dst: Option) -> Result { macro_rules! from_src { ($src:path) => { - impl From<$src> for RObjectOrSet<'_> { + impl From<$src> for ObjectOrSet<'_> { fn from(value: $src) -> Self { use $src; match value { diff --git a/rebacs_server/src/main.rs b/rebacserver/src/main.rs similarity index 89% rename from rebacs_server/src/main.rs rename to rebacserver/src/main.rs index 4f5aade..a3a30be 100644 --- a/rebacs_server/src/main.rs +++ b/rebacserver/src/main.rs @@ -1,11 +1,9 @@ -#![feature(btree_cursors)] - use std::{env, sync::Arc, time::Duration}; use grpc_service::RebacService; use jsonwebtoken::{Algorithm, DecodingKey, Validation}; use log::info; -use rebacs_core::RelationGraph; +use rebacdb::{ObjectOrSet, RelationGraph, Set}; use serde::Deserialize; use tokio::{ fs::{self, File}, @@ -96,3 +94,14 @@ async fn main() { .await .unwrap() } + +pub async fn can_write( + graph: &RelationGraph, + src: impl Into>, + dst: &Set, + limit: Option, +) -> bool { + graph + .check(src, &(dst.namespace(), dst.id(), "grant").into(), limit) + .await +}