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]]
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",

View file

@ -1,8 +1,8 @@
[workspace]
resolver = "2"
members = [
"rebacs_server",
"rebacs_core",
"rebacserver",
"rebacdb",
]
[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::{
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<String>,
}
/// 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<HashSet<Arc<Vertex>>>,
}
/// graph-based database implementation
#[derive(Default)]
pub struct RelationGraph {
/// all verticies of the graph
verticies: RwLock<BTreeSet<Arc<Vertex>>>,
}
@ -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<RObjectOrSet<'_>>, 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<ObjectOrSet<'_>>, 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<RObjectOrSet<'_>>, dst: &RSet) {
let src: RObjectOrSet<'_> = src.into();
/// remove a relation
pub async fn remove(&self, src: impl Into<ObjectOrSet<'_>>, 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<RObjectOrSet<'_>>, 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<ObjectOrSet<'_>>, 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<RObjectOrSet<'_>>,
dst: &RSet,
src: impl Into<ObjectOrSet<'_>>,
dst: &Set,
limit: Option<u32>,
) -> bool {
let src: RObjectOrSet<'_> = src.into();
let src: ObjectOrSet<'_> = src.into();
let mut distance = 1;
let mut neighbors: Vec<Arc<Vertex>> = 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<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>)> {
/// get all objects that are related to dst with the relation path
pub async fn expand(&self, dst: &Set) -> Vec<(Object, Vec<Set>)> {
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<String>)> for RObjectOrSet<'_> {
impl From<(String, String, Option<String>)> for ObjectOrSet<'_> {
fn from(value: (String, String, Option<String>)) -> 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<String>)> for RObjectOrSet<'_> {
}
}
impl Borrow<VertexId> for RSet {
impl Borrow<VertexId> for Set {
fn borrow(&self) -> &VertexId {
&self.0
}
}
impl PartialEq<VertexId> for RSet {
impl PartialEq<VertexId> for Set {
fn eq(&self, other: &VertexId) -> bool {
self.0.eq(other)
}
}
impl PartialEq<RSet> for VertexId {
fn eq(&self, other: &RSet) -> bool {
impl PartialEq<Set> for VertexId {
fn eq(&self, other: &Set) -> bool {
self.eq(&other.0)
}
}
impl Borrow<VertexId> for RObject {
impl Borrow<VertexId> for Object {
fn borrow(&self) -> &VertexId {
&self.0
}
}
impl From<RSet> for RObjectOrSet<'_> {
fn from(value: RSet) -> Self {
impl From<Set> for ObjectOrSet<'_> {
fn from(value: Set) -> Self {
Self::Set(Cow::Owned(value))
}
}
impl From<RObject> for RObjectOrSet<'_> {
fn from(value: RObject) -> Self {
impl From<Object> 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())),

View file

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

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]
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"

View file

@ -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<GrantReq>) -> Result<Response<GrantRes>, Status> {
let token =
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
let user: RObject = (USER_NS, token.claims.sub.as_str()).into();
let 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<RevokeReq>) -> Result<Response<RevokeRes>, Status> {
let token =
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
let user: RObject = (USER_NS, token.claims.sub.as_str()).into();
let 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<ExistsReq>) -> Result<Response<ExistsRes>, 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<Response<IsPermittedRes>, 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<impl Into<RObjectOrSet<'a>>>,
fallback_user: &'a RObject,
) -> Result<RObjectOrSet<'a>, Status> {
src: Option<impl Into<ObjectOrSet<'a>>>,
fallback_user: &'a DbObject,
) -> Result<ObjectOrSet<'a>, 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<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: 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<Set>) -> Result<RSet, Status> {
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 {

View file

@ -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<ObjectOrSet<'_>>,
dst: &Set,
limit: Option<u32>,
) -> bool {
graph
.check(src, &(dst.namespace(), dst.id(), "grant").into(), limit)
.await
}