mirror of
https://github.com/pfzetto/rebacs
synced 2024-11-23 11:42:50 +01:00
refactor
This commit is contained in:
parent
890c7bc29a
commit
bbf933a68b
12 changed files with 199 additions and 128 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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
38
README.md
Normal 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
16
rebacdb/Cargo.toml
Normal 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" ] }
|
|
@ -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())),
|
|
@ -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;
|
|
@ -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 = [] }
|
|
|
@ -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"
|
|
@ -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 {
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue