openid connect integration

This commit is contained in:
Paul Zinselmeyer 2023-08-27 22:10:16 +02:00
parent faf4e82f88
commit 66fac9d185
Signed by: pfzetto
GPG key ID: 4EEF46A5B276E648
8 changed files with 1304 additions and 1257 deletions

990
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,16 +6,24 @@ edition = "2021"
# 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
[dependencies] [dependencies]
dotenvy = "0.15.7"
log = "0.4.17"
env_logger = "0.10.0"
serde = { version="1.0", features=["derive"] } serde = { version="1.0", features=["derive"] }
tokio = { version = "1.27.0", features = ["full"] } tokio = { version = "1.27.0", features = ["full"] }
log = "0.4.17"
pretty_env_logger = "0.4.0"
dotenvy = "0.15.7"
tonic = { version="0.9.2", features=["tls"] } tonic = { version="0.9.2", features=["tls"] }
prost = "0.11.9" prost = "0.11.9"
sha2 = "0.10.6" sha2 = "0.10.6"
hex = "0.4.3" hex = "0.4.3"
compact_str = "0.7.0" compact_str = "0.7.0"
thiserror = "1.0.47"
jsonwebtoken = "8.3.0"
reqwest = { version="0.11.20", features=["json", "rustls-tls"], default-features=false}
[build-dependencies] [build-dependencies]
tonic-build = "0.9.2" tonic-build = "0.9.2"

129
flake.lock Normal file
View file

@ -0,0 +1,129 @@
{
"nodes": {
"crane": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": [
"rust-overlay"
]
},
"locked": {
"lastModified": 1691423162,
"narHash": "sha256-cReUZCo83YEEmFcHX8CcOVTZYUrcWgHQO34zxQzy7WI=",
"owner": "ipetkov",
"repo": "crane",
"rev": "b5d9d42ea3fa8fea1805d9af1416fe207d0dd1dc",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1691472822,
"narHash": "sha256-XVfYZ2oB3lNPVq6sHCY9WkdQ8lHoIDzzbpg8bB6oBxA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "41c7605718399dcfa53dd7083793b6ae3bc969ff",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1691547503,
"narHash": "sha256-l0AIKJucygbDFc2vuAkxmFMjNNJImDd7jYahA88/E+o=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "3380f16b39457b49c8186d5e20e7a68ccf4fc96e",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

77
flake.nix Normal file
View file

@ -0,0 +1,77 @@
{
description = "rebacs";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
crane = {
url = "github:ipetkov/crane";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
rust-overlay.follows = "rust-overlay";
};
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay, crane}:
flake-utils.lib.eachDefaultSystem
(system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
rustToolchain = pkgs.rust-bin.nightly.latest.default;
protoFilter = path: _type: builtins.match ".*proto$" path != null;
tailwindFilter = path: _type: builtins.match "^tailwind.config.js$" path != null;
protoOrCargo = path: type: (protoFilter path type) || (tailwindFilter path type) || (craneLib.filterCargoSources path type);
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
src = pkgs.lib.cleanSourceWith {
src = craneLib.path ./.;
filter = protoOrCargo;
};
nativeBuildInputs = with pkgs; [ rustToolchain pkg-config ];
buildInputs = with pkgs; [ protobuf ];
commonArgs = {
inherit src buildInputs nativeBuildInputs;
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
bin = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
});
dockerImage = pkgs.dockerTools.buildImage {
name = "rebacs";
tag = "latest";
config = {
Cmd = [ "${bin}/bin/rebacs" ];
};
};
in
with pkgs;
{
packages = {
inherit bin dockerImage;
default = bin;
};
devShells.default = mkShell {
inputsFrom = [ bin ];
};
}
);
}

View file

@ -1,95 +1,45 @@
syntax = "proto3"; syntax = "proto3";
package eu.zettoit.rebacs; package eu.zettoit.rebacs;
service RelationService { service RebacService {
rpc Create(RelationCreateReq) returns (RelationCreateRes); rpc Grant(GrantReq) returns (GrantRes);
rpc Delete(RelationDeleteReq) returns (RelationDeleteRes); rpc Revoke(RevokeReq) returns (RevokeRes);
rpc Exists(RelationExistsReq) returns (RelationExistsRes); rpc Exists(ExistsReq) returns (ExistsRes);
rpc IsPermitted(IsPermittedReq) returns (IsPermittedRes);
} }
service QueryService {
// check if one object or objectset is related to another by a relation
rpc IsRelatedTo(QueryIsRelatedToReq) returns (QueryIsRelatedToRes);
// get all objects that are related to one object by a relation message GrantReq{
rpc GetRelated(QueryGetRelatedReq) returns (QueryGetRelatedRes); Object src = 1;
// get all objects that the given object has a relation with
rpc GetRelations(QueryGetRelationsReq) returns (QueryGetRelationsRes);
}
message RelationCreateReq{
ObjectOrSet src = 1;
Object dst = 2; Object dst = 2;
string relation = 3;
} }
message RelationCreateRes{} message GrantRes{}
message RelationDeleteReq{ message RevokeReq{
ObjectOrSet src = 1; Object src = 1;
Object dst = 3;
string relation = 4;
}
message RelationDeleteRes{}
message RelationExistsReq{
ObjectOrSet src = 1;
Object dst = 2; Object dst = 2;
string relation = 3;
} }
message RelationExistsRes{ message RevokeRes{}
message ExistsReq{
Object src = 1;
Object dst = 2;
}
message ExistsRes{
bool exists = 1; bool exists = 1;
} }
message QueryIsRelatedToReq{ message IsPermittedReq{
ObjectOrSet src = 1;
Object dst = 2;
string relation = 3;
}
message QueryIsRelatedToRes{
bool related = 1;
}
message QueryGetRelatedReq{
Object dst = 1;
optional string relation = 2;
optional string namespace = 3;
optional uint32 depth = 4;
}
message QueryGetRelatedRes{
repeated QueryGetRelatedItem objects = 1;
}
message QueryGetRelatedItem{
string relation = 1;
Object src = 2;
}
message QueryGetRelationsReq{
Object src = 1; Object src = 1;
optional string relation = 2;
optional string namespace = 3;
optional uint32 depth = 4;
}
message QueryGetRelationsRes{
repeated QueryGetRelationsItem related = 1;
}
message QueryGetRelationsItem{
string relation = 1;
Object dst = 2; Object dst = 2;
} }
message IsPermittedRes{
bool permitted = 1;
}
message Object{ message Object{
string namespace = 1; string namespace = 1;
string id = 2; string id = 2;
}
message Set{
string namespace = 1;
string id = 2;
string relation = 3;
}
message ObjectOrSet {
string namespace = 1;
string id = 2;
optional string relation = 3; optional string relation = 3;
} }

View file

@ -1,421 +1,207 @@
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use jsonwebtoken::{decode, DecodingKey, TokenData, Validation};
use log::info; use log::info;
use sha2::{Digest, Sha256}; use serde::Deserialize;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use tokio::sync::Mutex;
use tonic::metadata::MetadataMap; use tonic::metadata::MetadataMap;
use tonic::{Request, Response, Status}; use tonic::{Request, Response, Status};
use crate::rebacs_proto::Object;
use crate::rebacs_proto::{ use crate::rebacs_proto::{
query_service_server::QueryService, relation_service_server::RelationService, Object, rebac_service_server, ExistsReq, ExistsRes, GrantReq, GrantRes, IsPermittedReq, IsPermittedRes,
QueryGetRelatedItem, QueryGetRelatedReq, QueryGetRelatedRes, QueryGetRelationsItem, RevokeReq, RevokeRes,
QueryGetRelationsReq, QueryGetRelationsRes, QueryIsRelatedToReq, QueryIsRelatedToRes,
RelationCreateReq, RelationCreateRes, RelationDeleteReq, RelationDeleteRes, RelationExistsReq,
RelationExistsRes,
}; };
use crate::relation_set::{ObjectOrSet, RelationSet}; use crate::relation_set::{NodeId, RelationSet};
#[derive(Clone)] #[derive(Clone)]
pub struct GraphService { pub struct RebacService {
pub api_keys: Arc<Mutex<HashMap<String, String>>>, pub graph: Arc<RelationSet>,
pub graph: Arc<Mutex<RelationSet>>, pub oidc_pubkey: DecodingKey,
pub oidc_validation: Validation,
pub save_trigger: Sender<()>, pub save_trigger: Sender<()>,
} }
const API_KEY_NS: &str = "rebacs_key"; const NAMESPACE_NS: &str = "namespace";
const NAMESPACE_NS: &str = "rebacs_ns"; const USER_NS: &str = "user";
const GRANT_RELATION: &str = "grant";
const REVOKE_RELATION: &str = "revoke";
#[tonic::async_trait] #[tonic::async_trait]
impl RelationService for GraphService { impl rebac_service_server::RebacService for RebacService {
async fn create( async fn grant(&self, request: Request<GrantReq>) -> Result<Response<GrantRes>, Status> {
&self, let token =
request: Request<RelationCreateReq>, extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
) -> Result<Response<RelationCreateRes>, Status> {
let mut graph = self.graph.lock().await;
let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
let req_src = request if !is_permitted(&token, &dst, GRANT_RELATION, &self.graph).await {
.get_ref()
.src
.as_ref()
.ok_or(Status::invalid_argument("src must be set"))?;
let req_dst = request
.get_ref()
.dst
.as_ref()
.ok_or(Status::invalid_argument("dst must be set"))?;
let req_rel = &request.get_ref().relation;
if req_rel.is_empty() {
return Err(Status::invalid_argument("relation must be set"));
}
if req_dst.namespace.is_empty() {
return Err(Status::invalid_argument("dst.namespace must be set"));
}
if req_dst.id.is_empty() {
return Err(Status::invalid_argument("dst.id must be set"));
}
if !graph.has_recursive(
(API_KEY_NS, &*api_key),
"write",
(NAMESPACE_NS, &*req_dst.namespace),
u32::MAX,
) {
return Err(Status::permission_denied( return Err(Status::permission_denied(
"missing dst.namespace write permissions", "token not permitted to grant permissions on dst",
))?; ));
} }
if req_src.namespace.is_empty() { info!(
return Err(Status::invalid_argument("src.namespace must be set")); "created relation {}:{}#{}@{}:{}#{} for {}",
} dst.namespace,
if req_src.id.is_empty() { dst.id,
return Err(Status::invalid_argument("src.id must be set")); dst.relation.clone().unwrap_or_default(),
} src.namespace,
let src: ObjectOrSet = if let Some(req_src_relation) = req_src.relation.as_deref() { src.id,
if req_src_relation.is_empty() { src.relation.clone().unwrap_or_default(),
return Err(Status::invalid_argument("src.relation must be set")); token.claims.sub
}
(&*req_src.namespace, &*req_src.id, req_src_relation).into()
} else {
(&*req_src.namespace, &*req_src.id).into()
};
graph.insert(
src.clone(),
req_rel.clone(),
(req_dst.namespace.clone(), req_dst.id.clone()),
); );
info!("created relation"); self.graph.insert(src, dst).await;
self.save_trigger.send(()).await.unwrap(); self.save_trigger.send(()).await.unwrap();
Ok(Response::new(RelationCreateRes {})) Ok(Response::new(GrantRes {}))
} }
async fn delete( async fn revoke(&self, request: Request<RevokeReq>) -> Result<Response<RevokeRes>, Status> {
&self, let token =
request: Request<RelationDeleteReq>, extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
) -> Result<Response<RelationDeleteRes>, Status> {
let mut graph = self.graph.lock().await;
let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
let req_src = request if !is_permitted(&token, &dst, REVOKE_RELATION, &self.graph).await {
.get_ref()
.src
.as_ref()
.ok_or(Status::invalid_argument("src must be set"))?;
let req_dst = request
.get_ref()
.dst
.as_ref()
.ok_or(Status::invalid_argument("dst must be set"))?;
let req_rel = &request.get_ref().relation;
if req_rel.is_empty() {
return Err(Status::invalid_argument("relation must be set"));
}
if req_dst.namespace.is_empty() {
return Err(Status::invalid_argument("dst.namespace must be set"));
}
if req_dst.id.is_empty() {
return Err(Status::invalid_argument("dst.id must be set"));
}
if !graph.has_recursive(
(API_KEY_NS, &*api_key),
"write",
(NAMESPACE_NS, &*req_dst.namespace),
u32::MAX,
) {
return Err(Status::permission_denied( return Err(Status::permission_denied(
"missing dst.namespace write permissions", "token not permitted to revoke permissions on dst",
))?; ));
} }
if req_src.namespace.is_empty() { self.graph
return Err(Status::invalid_argument("src.namespace must be set")); .remove(
} (
if req_src.id.is_empty() { src.namespace.to_string(),
return Err(Status::invalid_argument("src.id must be set")); src.id.to_string(),
} src.relation.clone(),
let src: ObjectOrSet = if let Some(req_src_relation) = req_src.relation.as_deref() { ),
if req_src_relation.is_empty() { (dst.namespace.clone(), dst.id.clone(), dst.relation.clone()),
return Err(Status::invalid_argument("src.relation must be set")); )
} .await;
(&*req_src.namespace, &*req_src.id, req_src_relation).into() info!(
} else { "delted relation {}:{}#{}@{}:{}#{} for {}",
(&*req_src.namespace, &*req_src.id).into() dst.namespace,
}; dst.id,
dst.relation.clone().unwrap_or_default(),
graph.remove(src, req_rel.as_str(), (&*req_dst.namespace, &*req_dst.id)); src.namespace,
src.id,
info!("deleted relation"); src.relation.clone().unwrap_or_default(),
token.claims.sub
);
self.save_trigger.send(()).await.unwrap(); self.save_trigger.send(()).await.unwrap();
Ok(Response::new(RelationDeleteRes {})) Ok(Response::new(RevokeRes {}))
} }
async fn exists( 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 (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
let exists = self.graph.has(src, dst).await;
Ok(Response::new(ExistsRes { exists }))
}
async fn is_permitted(
&self, &self,
request: Request<RelationExistsReq>, request: Request<IsPermittedReq>,
) -> Result<Response<RelationExistsRes>, Status> { ) -> Result<Response<IsPermittedRes>, Status> {
let graph = self.graph.lock().await; let token =
extract_token(request.metadata(), &self.oidc_pubkey, &self.oidc_validation).await?;
let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; let (src, dst) = extract_src_dst(&request.get_ref().src, &request.get_ref().dst)?;
let req_src = request let permitted = self.graph.has_recursive(src, dst, None).await;
.get_ref()
.src
.as_ref()
.ok_or(Status::invalid_argument("src must be set"))?;
let req_dst = request
.get_ref()
.dst
.as_ref()
.ok_or(Status::invalid_argument("dst must be set"))?;
let req_rel = &request.get_ref().relation;
if req_rel.is_empty() { Ok(Response::new(IsPermittedRes { permitted }))
return Err(Status::invalid_argument("relation must be set"));
}
if req_dst.namespace.is_empty() {
return Err(Status::invalid_argument("dst.namespace must be set"));
}
if req_dst.id.is_empty() {
return Err(Status::invalid_argument("dst.id must be set"));
}
if !graph.has_recursive(
(API_KEY_NS, &*api_key),
"read",
(NAMESPACE_NS, &*req_dst.namespace),
u32::MAX,
) {
return Err(Status::permission_denied(
"missing dst.namespace write permissions",
))?;
}
if req_src.namespace.is_empty() {
return Err(Status::invalid_argument("src.namespace must be set"));
}
if req_src.id.is_empty() {
return Err(Status::invalid_argument("src.id must be set"));
}
let src: ObjectOrSet = if let Some(req_src_relation) = req_src.relation.as_deref() {
if req_src_relation.is_empty() {
return Err(Status::invalid_argument("src.relation must be set"));
}
(&*req_src.namespace, &*req_src.id, req_src_relation).into()
} else {
(&*req_src.namespace, &*req_src.id).into()
};
let exists = graph.has(src, req_rel.as_str(), (&*req_dst.namespace, &*req_dst.id));
Ok(Response::new(RelationExistsRes { exists }))
} }
} }
#[tonic::async_trait] #[derive(Debug, Clone, Deserialize)]
impl QueryService for GraphService { pub struct Claims {
async fn is_related_to( pub aud: Vec<String>,
&self, pub exp: usize,
request: Request<QueryIsRelatedToReq>, pub iat: usize,
) -> Result<Response<QueryIsRelatedToRes>, Status> { pub iss: String,
let graph = self.graph.lock().await; pub sub: String,
pub azp: String,
let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?; pub name: String,
pub preferred_username: String,
let req_src = request pub given_name: String,
.get_ref() pub family_name: String,
.src pub email: String,
.as_ref()
.ok_or(Status::invalid_argument("src must be set"))?;
let req_dst = request
.get_ref()
.dst
.as_ref()
.ok_or(Status::invalid_argument("dst must be set"))?;
let req_rel = &request.get_ref().relation;
if req_rel.is_empty() {
return Err(Status::invalid_argument("relation must be set"));
}
if req_dst.namespace.is_empty() {
return Err(Status::invalid_argument("dst.namespace must be set"));
}
if req_dst.id.is_empty() {
return Err(Status::invalid_argument("dst.id must be set"));
}
if !graph.has_recursive(
(API_KEY_NS, &*api_key),
"read",
(NAMESPACE_NS, &*req_dst.namespace),
u32::MAX,
) {
return Err(Status::permission_denied(
"missing dst.namespace read permissions",
))?;
}
if req_src.namespace.is_empty() {
return Err(Status::invalid_argument("src.namespace must be set"));
}
if req_src.id.is_empty() {
return Err(Status::invalid_argument("src.id must be set"));
}
let src: ObjectOrSet = if let Some(req_src_relation) = req_src.relation.as_deref() {
if req_src_relation.is_empty() {
return Err(Status::invalid_argument("src.relation must be set"));
}
(&*req_src.namespace, &*req_src.id, req_src_relation).into()
} else {
(&*req_src.namespace, &*req_src.id).into()
};
let related = graph.has_recursive(
src,
req_rel.as_str(),
(&*req_dst.namespace, &*req_dst.id),
u32::MAX,
);
Ok(Response::new(QueryIsRelatedToRes { related }))
}
async fn get_related(
&self,
request: Request<QueryGetRelatedReq>,
) -> Result<Response<QueryGetRelatedRes>, Status> {
let graph = self.graph.lock().await;
let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?;
let req_dst = request
.get_ref()
.dst
.as_ref()
.ok_or(Status::invalid_argument("dst must be set"))?;
let req_rel = &request.get_ref().relation;
if req_dst.namespace.is_empty() {
return Err(Status::invalid_argument("dst.namespace must be set"));
}
if req_dst.id.is_empty() {
return Err(Status::invalid_argument("dst.id must be set"));
}
let req_namespace = &request.get_ref().namespace;
let req_depth = &request.get_ref().depth;
if !graph.has_recursive(
(API_KEY_NS, &*api_key),
"read",
(NAMESPACE_NS, &*req_dst.namespace),
u32::MAX,
) {
return Err(Status::permission_denied(
"missing dst.namespace read permissions",
))?;
}
let dst = (req_dst.namespace.as_ref(), req_dst.id.as_ref());
let objects = graph
.related_to(
dst,
req_rel.as_deref(),
req_namespace.as_deref(),
req_depth.unwrap_or(u32::MAX),
)
.into_iter()
.map(|x| QueryGetRelatedItem {
src: Some(Object {
namespace: x.1.namespace.to_string(),
id: x.1.id.to_string(),
}),
relation: x.0 .0.to_string(),
})
.collect::<_>();
Ok(Response::new(QueryGetRelatedRes { objects }))
}
async fn get_relations(
&self,
request: Request<QueryGetRelationsReq>,
) -> Result<Response<QueryGetRelationsRes>, Status> {
let graph = self.graph.lock().await;
let api_key = api_key_from_req(request.metadata(), &self.api_keys).await?;
let req_src = request
.get_ref()
.src
.as_ref()
.ok_or(Status::invalid_argument("src must be set"))?;
let src = (&*req_src.namespace, &*req_src.id);
let req_rel = &request.get_ref().relation;
let req_namespace = &request.get_ref().namespace;
let req_depth = &request.get_ref().depth;
if !graph.has_recursive(
(API_KEY_NS, &*api_key),
"read",
(NAMESPACE_NS, &*req_src.namespace),
u32::MAX,
) {
return Err(Status::permission_denied(
"missing src.namespace read permissions",
))?;
}
let related = graph
.relations(
src,
req_rel.as_deref(),
req_namespace.as_deref(),
req_depth.unwrap_or(u32::MAX),
)
.into_iter()
.map(|x| QueryGetRelationsItem {
dst: Some(Object {
namespace: x.1.namespace.to_string(),
id: x.1.id.to_string(),
}),
relation: x.0 .0.to_string(),
})
.collect::<_>();
Ok(Response::new(QueryGetRelationsRes { related }))
}
} }
async fn api_key_from_req( async fn extract_token(
metadata: &MetadataMap, metadata: &MetadataMap,
api_keys: &Arc<Mutex<HashMap<String, String>>>, pubkey: &DecodingKey,
) -> Result<String, Status> { validation: &Validation,
let api_key = metadata ) -> Result<TokenData<Claims>, Status> {
.get("x-api-key") let token = metadata
.get("authorization")
.map(|x| x.to_str().unwrap()) .map(|x| x.to_str().unwrap())
.ok_or(Status::unauthenticated("x-api-key required"))?; .ok_or(Status::unauthenticated("authorization header required"))?;
let mut hasher = Sha256::new(); let token = decode::<Claims>(token, pubkey, validation)
hasher.update(api_key); .map_err(|_| Status::unauthenticated("authorization header invalid"))?;
let api_key = hex::encode(hasher.finalize());
let api_keys = api_keys.lock().await; Ok(token)
let api_key = api_keys }
.get(&api_key)
.ok_or(Status::unauthenticated("api-key invalid"))?; async fn is_permitted(
Ok(api_key.to_string()) token: &TokenData<Claims>,
dst: &NodeId,
relation: &str,
graph: &RelationSet,
) -> bool {
let s1 = graph
.has_recursive(
(USER_NS, token.claims.sub.as_str()),
(dst.namespace.as_str(), dst.id.as_str(), relation),
None,
)
.await;
let s2 = graph
.has_recursive(
(USER_NS, token.claims.sub.as_str()),
(NAMESPACE_NS, dst.namespace.as_str(), relation),
None,
)
.await;
s1 || s2
}
fn extract_src_dst(src: &Option<Object>, dst: &Option<Object>) -> Result<(NodeId, NodeId), Status> {
let src = src
.as_ref()
.ok_or(Status::invalid_argument("src must be set"))?;
let src: NodeId = (src.namespace.clone(), src.id.clone(), src.relation.clone()).into();
let dst = dst
.as_ref()
.ok_or(Status::invalid_argument("dst must be set"))?;
let dst: NodeId = (dst.namespace.clone(), dst.id.clone(), dst.relation.clone()).into();
if dst.namespace.is_empty() {
return Err(Status::invalid_argument("dst.namespace must be set"));
}
if dst.id.is_empty() {
return Err(Status::invalid_argument("dst.id must be set"));
}
if src.namespace.is_empty() {
return Err(Status::invalid_argument("src.namespace must be set"));
}
if src.id.is_empty() {
return Err(Status::invalid_argument("src.id must be set"));
}
Ok((src, dst))
} }

View file

@ -1,15 +1,16 @@
#![feature(btree_cursors)] #![feature(btree_cursors)]
use std::{collections::HashMap, sync::Arc, time::Duration}; use std::{env, sync::Arc, time::Duration};
use grpc_service::GraphService; use grpc_service::RebacService;
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
use log::info;
use relation_set::RelationSet; use relation_set::RelationSet;
//use grpc_service::GraphService; use serde::Deserialize;
use tokio::{ use tokio::{
fs::{self, File}, fs::{self, File},
io::{AsyncBufReadExt, BufReader},
select, select,
sync::{mpsc::channel, Mutex}, sync::mpsc::channel,
}; };
use tonic::transport::Server; use tonic::transport::Server;
@ -17,35 +18,26 @@ pub mod grpc_service;
pub mod rebacs_proto; pub mod rebacs_proto;
pub mod relation_set; pub mod relation_set;
use crate::rebacs_proto::{ use crate::rebacs_proto::rebac_service_server;
query_service_server::QueryServiceServer, relation_service_server::RelationServiceServer,
}; #[derive(Deserialize)]
struct IssuerDiscovery {
public_key: String,
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
pretty_env_logger::init(); env_logger::init();
let mut api_keys = HashMap::new();
if let Ok(file) = File::open("api_keys.dat").await {
let reader = BufReader::new(file);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
let line = line.replace(' ', "");
let mut line = line.split('=');
let name = line.next().unwrap().to_string();
let hash = line.next().unwrap().to_string();
api_keys.insert(hash, name);
}
}
info!("loading graph from graph.dat");
let graph = if let Ok(mut file) = File::open("graph.dat").await { let graph = if let Ok(mut file) = File::open("graph.dat").await {
RelationSet::from_file(&mut file).await RelationSet::from_file(&mut file).await
} else { } else {
RelationSet::new() RelationSet::default()
}; };
let graph = Arc::new(Mutex::new(graph)); let graph = Arc::new(graph);
let (save_tx, mut save_rx) = channel::<()>(32); let (save_tx, mut save_rx) = channel::<()>(32);
let save_thread_graph = graph.clone(); let save_thread_graph = graph.clone();
@ -55,24 +47,48 @@ async fn main() {
_ = tokio::time::sleep(Duration::from_secs(30)) => {} _ = tokio::time::sleep(Duration::from_secs(30)) => {}
_ = save_rx.recv() => {} _ = save_rx.recv() => {}
}; };
let graph = save_thread_graph.lock().await; info!("saving graph");
let _ = fs::copy("graph.dat", "graph.dat.bak").await; let _ = fs::copy("graph.dat", "graph.dat.bak").await;
let mut file = File::create("graph.dat").await.unwrap(); let mut file = File::create("graph.dat").await.unwrap();
graph.to_file(&mut file).await; save_thread_graph.to_file(&mut file).await;
} }
}); });
let graph_service = GraphService { let issuer = env::var("OIDC_ISSUER").expect("OIDC_ISSUER env var");
api_keys: Arc::new(Mutex::new(api_keys)), info!("loading public key from {issuer}");
let issuer_key = reqwest::get(&issuer)
.await
.unwrap()
.json::<IssuerDiscovery>()
.await
.unwrap()
.public_key;
let pem = format!(
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----",
issuer_key
);
let oidc_pubkey = DecodingKey::from_rsa_pem(pem.as_bytes()).unwrap();
let mut oidc_validation = Validation::new(Algorithm::RS256);
oidc_validation.set_issuer(&[&issuer]);
oidc_validation.set_audience(&[env::var("OIDC_AUDIENCE").expect("OIDC_AUDIENCE env var")]);
let rebac_service = RebacService {
graph: graph.clone(), graph: graph.clone(),
save_trigger: save_tx.clone(), save_trigger: save_tx.clone(),
oidc_pubkey,
oidc_validation,
}; };
let listen = "[::]:50051";
info!("starting grpc server on {listen}");
Server::builder() Server::builder()
.add_service(RelationServiceServer::new(graph_service.clone())) .add_service(rebac_service_server::RebacServiceServer::new(
.add_service(QueryServiceServer::new(graph_service)) rebac_service.clone(),
.serve("[::]:50051".parse().unwrap()) ))
.serve(listen.parse().unwrap())
.await .await
.unwrap() .unwrap()
} }

View file

@ -1,399 +1,197 @@
use std::{ use std::{
borrow::Borrow,
cmp::Ordering, cmp::Ordering,
collections::{BTreeMap, BinaryHeap, HashMap, HashSet}, collections::{BTreeSet, BinaryHeap, HashSet},
ops::{Bound, Deref}, fmt::Debug,
hash::Hash,
ops::Deref,
sync::Arc, sync::Arc,
}; };
use compact_str::CompactString;
use tokio::{ use tokio::{
fs::File, fs::File,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
sync::RwLock,
}; };
#[derive(Hash, PartialEq, Eq, Clone, Debug)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Object { pub struct NodeId {
pub namespace: CompactString, pub namespace: String,
pub id: CompactString, pub id: String,
pub relation: Option<String>,
} }
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] pub struct Node {
pub struct ObjectRef<'a> { pub id: NodeId,
pub namespace: &'a str, pub edges_in: RwLock<Vec<Arc<Node>>>,
pub id: &'a str, pub edges_out: RwLock<Vec<Arc<Node>>>,
} }
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(Debug, PartialEq, Eq)]
pub enum ObjectOrSet { struct Distanced<T> {
Object(Object), distance: u32,
Set((Object, Relation)), data: T,
} }
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
pub struct Relation(pub CompactString);
type S = ObjectOrSet;
type R = Relation;
type D = Object;
#[derive(Default)]
pub struct RelationSet { pub struct RelationSet {
src_to_dst: BTreeMap<Arc<S>, HashMap<Arc<R>, HashSet<Arc<D>>>>, nodes: RwLock<BTreeSet<Arc<Node>>>,
dst_to_src: BTreeMap<Arc<D>, HashMap<Arc<R>, HashSet<Arc<S>>>>,
} }
impl RelationSet { impl RelationSet {
pub fn new() -> Self { pub async fn insert(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) {
Self {
src_to_dst: BTreeMap::new(),
dst_to_src: BTreeMap::new(),
}
}
pub fn insert(&mut self, src: impl Into<S>, rel: impl Into<R>, dst: impl Into<D>) {
let src = Arc::new(src.into());
let rel = Arc::new(rel.into());
let dst = Arc::new(dst.into());
if let Some(rels_dsts) = self.src_to_dst.get_mut(&src) {
if let Some(dsts) = rels_dsts.get_mut(&rel) {
dsts.insert(dst.clone());
} else {
let mut dsts = HashSet::new();
dsts.insert(dst.clone());
rels_dsts.insert(rel.clone(), dsts);
}
} else {
let mut rels_dsts = HashMap::new();
let mut dsts = HashSet::new();
dsts.insert(dst.clone());
rels_dsts.insert(rel.clone(), dsts);
self.src_to_dst.insert(src.clone(), rels_dsts);
}
if let Some(rels_srcs) = self.dst_to_src.get_mut(&dst) {
if let Some(srcs) = rels_srcs.get_mut(&rel) {
srcs.insert(src.clone());
} else {
let mut srcs = HashSet::new();
srcs.insert(src.clone());
rels_srcs.insert(rel.clone(), srcs);
}
} else {
let mut rels_srcs = HashMap::new();
let mut srcs = HashSet::new();
srcs.insert(src.clone());
rels_srcs.insert(rel.clone(), srcs);
self.dst_to_src.insert(dst.clone(), rels_srcs);
}
}
pub fn remove(&mut self, src: impl Into<S>, rel: impl Into<R>, dst: impl Into<D>) {
let src = src.into(); let src = src.into();
let rel = rel.into();
let dst = dst.into(); let dst = dst.into();
if let Some(dsts) = self let mut nodes = self.nodes.write().await;
.src_to_dst
.get_mut(&src)
.and_then(|rels_dsts| rels_dsts.get_mut(&rel))
{
dsts.remove(&dst);
}
if let Some(srcs) = self let src_node = match nodes.get(&src) {
.dst_to_src Some(node) => node.clone(),
.get_mut(&dst) None => {
.and_then(|rels_srcs| rels_srcs.get_mut(&rel)) let node = Arc::new(Node {
{ id: src,
srcs.remove(&src); edges_out: RwLock::new(vec![]),
} edges_in: RwLock::new(vec![]),
} });
nodes.insert(node.clone());
pub fn remove_by_src(&mut self, src: &S) { node
for (rel, dsts) in self.src_to_dst.remove(src).iter().flat_map(|x| x.iter()) {
for dst in dsts {
if let Some(srcs) = self
.dst_to_src
.get_mut(dst)
.and_then(|rels_srcs| rels_srcs.get_mut(rel))
{
srcs.remove(src);
}
} }
} };
} let dst_node = match nodes.get(&dst).cloned() {
Some(node) => node.clone(),
pub fn remove_by_dst(&mut self, dst: &D) { None => {
for (rel, srcs) in self.dst_to_src.remove(dst).iter().flat_map(|x| x.iter()) { let node = Arc::new(Node {
for src in srcs { id: dst,
if let Some(dsts) = self edges_out: RwLock::new(vec![]),
.src_to_dst edges_in: RwLock::new(vec![]),
.get_mut(src) });
.and_then(|rels_dsts| rels_dsts.get_mut(rel)) nodes.insert(node.clone());
{ node
dsts.remove(dst);
}
} }
} };
add_edge(src_node, dst_node).await;
} }
pub fn has(&self, src: impl Into<S>, rel: impl Into<R>, dst: impl Into<D>) -> bool { pub async fn remove(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) {
let src = src.into(); let src = src.into();
let rel = rel.into();
let dst = dst.into(); let dst = dst.into();
self.src_to_dst let mut nodes = self.nodes.write().await;
.get(&src)
.and_then(|rels_dsts| rels_dsts.get(&rel)) let src = nodes.get(&src).cloned();
.and_then(|dsts| dsts.get(&dst)) let dst = nodes.get(&dst).cloned();
.is_some()
if let (Some(src), Some(dst)) = (src, dst) {
src.edges_out.write().await.retain(|x| x != &dst);
dst.edges_in.write().await.retain(|x| x != &src);
if src.edges_in.read().await.is_empty() && src.edges_out.read().await.is_empty() {
nodes.remove(&src.id);
}
if dst.edges_in.read().await.is_empty() && dst.edges_out.read().await.is_empty() {
nodes.remove(&dst.id);
}
}
} }
pub fn has_object<'a>(&self, obj: impl Into<&'a Object>) -> bool { pub async fn has(&self, src: impl Into<NodeId>, dst: impl Into<NodeId>) -> bool {
let obj = obj.into(); let src = src.into();
let has_dst_obj = self.dst_to_src.contains_key(obj); let dst = dst.into();
let cursor = self let (src, dst) = {
.src_to_dst let nodes = self.nodes.read().await;
.lower_bound(Bound::Included(&ObjectOrSet::Object(obj.clone()))); (nodes.get(&src).cloned(), nodes.get(&dst).cloned())
let has_src_obj = if let Some(key) = cursor.key() {
obj.namespace == key.object().namespace && obj.id == key.object().id
} else {
false
}; };
has_dst_obj || has_src_obj if let (Some(src), Some(dst)) = (src, dst) {
src.edges_out.read().await.contains(&dst)
} else {
false
}
} }
pub fn has_recursive( pub async fn has_recursive<'a>(
&self, &self,
src: impl Into<S>, src: impl Into<NodeId>,
rel: impl Into<R>, dst: impl Into<NodeId>,
dst: impl Into<D>, limit: Option<u32>,
limit: u32,
) -> bool { ) -> bool {
let src = src.into(); let src = src.into();
let rel = rel.into();
let dst = dst.into(); let dst = dst.into();
let mut dist: HashMap<(Arc<Object>, Arc<Relation>), u32> = HashMap::new(); let src = self.nodes.read().await.get(&src).unwrap().clone();
let mut q: BinaryHeap<Distanced<(Arc<Object>, Arc<Relation>)>> = BinaryHeap::new();
for (nrel, ndst) in self let src_neighbors = src
.src_to_dst .edges_out
.get(&src) .read()
.await
.iter() .iter()
.flat_map(|x| x.iter()) .map(|x| Distanced::one(x.clone()))
.flat_map(|(r, d)| d.iter().map(|d| (r.clone(), d.clone()))) .collect::<Vec<_>>();
{
if *nrel == rel && *ndst == dst { let mut q: BinaryHeap<Distanced<Arc<Node>>> = BinaryHeap::from(src_neighbors);
return true; let mut visited: HashSet<Arc<Node>> = HashSet::new();
}
dist.insert((ndst.clone(), nrel.clone()), 1);
q.push(Distanced::one((ndst, nrel)));
}
while let Some(distanced) = q.pop() { while let Some(distanced) = q.pop() {
let node_dist = distanced.distance() + 1; if distanced.id == dst {
if node_dist > limit { return true;
break;
} }
let node = ObjectOrSet::Set(((*distanced.0).clone(), (*distanced.1).clone())); if let Some(limit) = limit {
for (nrel, ndst) in self if distanced.distance() > limit {
.src_to_dst return false;
.get(&node)
.iter()
.flat_map(|x| x.iter())
.flat_map(|(r, d)| d.iter().map(|d| (r.clone(), d.clone())))
{
if *nrel == rel && *ndst == dst {
return true;
} }
if let Some(existing_node_dist) = dist.get(&*distanced) {
if *existing_node_dist <= node_dist {
continue;
}
}
dist.insert((ndst.clone(), nrel.clone()), node_dist);
q.push(Distanced::one((ndst, nrel)));
} }
for neighbor in distanced.edges_out.read().await.iter() {
if !visited.contains(neighbor) {
q.push(Distanced::new(neighbor.clone(), distanced.distance() + 1))
}
}
visited.insert(distanced.clone());
} }
false false
} }
pub fn related_to(
&self,
dst: impl Into<D>,
rel: Option<impl Into<R>>,
namespace: Option<&str>,
limit: u32,
) -> Vec<(Relation, Object)> {
let rel = rel.map(|x| x.into());
let dst = dst.into();
let mut related: Vec<(Relation, Object)> = vec![];
let mut dist: HashMap<(Arc<Object>, Arc<Relation>), u32> = HashMap::new();
let mut q: BinaryHeap<Distanced<(Arc<Object>, Arc<Relation>)>> = BinaryHeap::new();
for (nrel, ndst) in self
.dst_to_src
.get(&dst)
.iter()
.flat_map(|x| x.iter())
.flat_map(|(r, d)| d.iter().map(|d| (r.clone(), d.clone())))
{
match &*ndst {
ObjectOrSet::Object(obj) => {
if (rel.is_none() || rel.as_ref() == Some(&nrel))
&& (namespace.is_none() || namespace == Some(&obj.namespace))
{
related.push(((*nrel).clone(), obj.clone()));
}
}
ObjectOrSet::Set((obj, rel)) => {
let obj = Arc::new(obj.clone());
let rel = Arc::new(rel.clone());
dist.insert((obj.clone(), rel.clone()), 1);
q.push(Distanced::one((obj, rel)));
}
}
}
while let Some(distanced) = q.pop() {
let node_dist = distanced.distance() + 1;
if node_dist > limit {
break;
}
for ndst in self
.dst_to_src
.get(&distanced.0)
.and_then(|x| x.get(&distanced.1))
.iter()
.flat_map(|x| x.iter())
{
match &**ndst {
ObjectOrSet::Object(obj) => {
if (rel.is_none() || rel.as_ref() == Some(&distanced.1))
&& (namespace.is_none() || namespace == Some(&obj.namespace))
{
related.push(((*distanced.1).clone(), obj.clone()));
}
}
ObjectOrSet::Set((obj, rel)) => {
let obj = Arc::new(obj.clone());
let rel = Arc::new(rel.clone());
dist.insert((obj.clone(), rel.clone()), node_dist);
q.push(Distanced::one((obj, rel)));
}
}
}
}
related
}
pub fn relations(
&self,
src: impl Into<S>,
rel: Option<impl Into<R>>,
namespace: Option<&str>,
limit: u32,
) -> Vec<(Relation, Object)> {
let rel = rel.map(|x| x.into());
let src = src.into();
let mut related: Vec<(Relation, Object)> = vec![];
let mut dist: HashMap<Arc<ObjectOrSet>, u32> = HashMap::new();
let mut q: BinaryHeap<Distanced<Arc<ObjectOrSet>>> = BinaryHeap::new();
for (nrel, ndst) in self
.src_to_dst
.get(&src)
.iter()
.flat_map(|x| x.iter())
.flat_map(|(r, d)| d.iter().map(|d| (r.clone(), d.clone())))
{
if (rel.is_none() || rel.as_ref() == Some(&nrel))
&& (namespace.is_none() || namespace == Some(&ndst.namespace))
{
related.push(((*nrel).clone(), (*ndst).clone()));
}
let obj = Arc::new(ObjectOrSet::Set(((*ndst).clone(), (*nrel).clone())));
dist.insert(obj.clone(), 1);
q.push(Distanced::one(obj));
}
while let Some(distanced) = q.pop() {
let node_dist = distanced.distance() + 1;
if node_dist > limit {
break;
}
for (nrel, ndsts) in self
.src_to_dst
.get(&*distanced)
.iter()
.flat_map(|x| x.iter())
{
for ndst in ndsts {
if (rel.is_none() || rel.as_ref() == Some(nrel))
&& (namespace.is_none() || namespace == Some(&ndst.namespace))
{
related.push(((**nrel).clone(), (**ndst).clone()));
}
let obj = Arc::new(ObjectOrSet::Set(((**ndst).clone(), (**nrel).clone())));
dist.insert(obj.clone(), node_dist);
q.push(Distanced::one(obj));
}
}
}
related
}
pub async fn to_file(&self, file: &mut File) { pub async fn to_file(&self, file: &mut File) {
for (dst, rels_srcs) in self.dst_to_src.iter() { let mut current: (String, String) = (String::new(), String::new());
file.write_all(format!("[{}:{}]\n", &dst.namespace, &dst.id).as_bytes()) for node in self.nodes.read().await.iter() {
.await if current != (node.id.namespace.clone(), node.id.id.clone()) {
.unwrap(); current = (node.id.namespace.clone(), node.id.id.clone());
for (rel, srcs) in rels_srcs.iter() { file.write_all("\n".as_bytes()).await.unwrap();
if srcs.is_empty() { file.write_all(format!("[{}:{}]\n", &current.0, &current.1).as_bytes())
continue; .await
} .unwrap();
let srcs = srcs }
.iter()
.map(|src| { let srcs = node
let src_obj = src.object(); .edges_in
let src_str = if src_obj.namespace == dst.namespace && src_obj.id == dst.id .read()
{ .await
"self".to_string() .iter()
} else { .map(|src| {
format!("{}:{}", src_obj.namespace, src_obj.id) if src.id.namespace == current.0 && src.id.id == current.1 {
}; "self".to_string()
match &**src { } else if let Some(rel) = &src.id.relation {
ObjectOrSet::Object(_) => src_str, format!("{}:{}#{}", &src.id.namespace, &src.id.id, &rel)
ObjectOrSet::Set(set) => { } else {
format!("{}#{}", src_str, set.1 .0) format!("{}:{}", &src.id.namespace, &src.id.id)
} }
} })
}) .reduce(|acc, x| acc + ", " + &x)
.reduce(|acc, x| acc + ", " + &x) .unwrap_or_default();
.unwrap_or_default();
if let Some(rel) = &node.id.relation {
file.write_all(format!("{} = [{}]\n", &rel.0, &srcs).as_bytes()) file.write_all(format!("{} = [ {} ]\n", &rel, &srcs).as_bytes())
.await .await
.unwrap(); .unwrap();
} }
file.write_all("\n".as_bytes()).await.unwrap();
} }
} }
pub async fn from_file(file: &mut File) -> Self { pub async fn from_file(file: &mut File) -> Self {
let reader = BufReader::new(file); let reader = BufReader::new(file);
let mut lines = reader.lines(); let mut lines = reader.lines();
let mut graph = Self::new(); let graph = Self::default();
let mut node: Option<(String, String)> = None; let mut node: Option<(String, String)> = None;
while let Ok(Some(line)) = lines.next_line().await { while let Ok(Some(line)) = lines.next_line().await {
if line.starts_with('[') && line.ends_with(']') { if line.starts_with('[') && line.ends_with(']') {
@ -408,10 +206,10 @@ impl RelationSet {
let arr_stop = line.find(']').unwrap(); let arr_stop = line.find(']').unwrap();
let rel = line[..equals_pos].trim(); let rel = line[..equals_pos].trim();
let arr = line[arr_start + 1..arr_stop].split(", "); let arr = line[arr_start + 1..arr_stop].trim().split(", ");
for obj in arr { for obj in arr {
let src: ObjectOrSet = if obj.contains('#') { let src: NodeId = 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();
@ -435,7 +233,9 @@ impl RelationSet {
(namespace, id).into() (namespace, id).into()
}; };
graph.insert(src, rel, dst.clone()); graph
.insert(src, (dst.0.as_str(), dst.1.as_str(), rel))
.await;
} }
} }
} }
@ -444,13 +244,51 @@ impl RelationSet {
} }
} }
#[derive(PartialEq, Eq)] impl Debug for Node {
struct Distanced<T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
distance: u32, f.debug_struct("Node").field("id", &self.id).finish()
data: T, }
}
async fn add_edge(from: Arc<Node>, to: Arc<Node>) {
from.edges_out.write().await.push(to.clone());
to.edges_in.write().await.push(from);
}
impl Borrow<NodeId> for Arc<Node> {
fn borrow(&self) -> &NodeId {
&self.id
}
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Node {}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.id.partial_cmp(&other.id)
}
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl Hash for Node {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
} }
impl<T> Distanced<T> { impl<T> Distanced<T> {
pub fn new(data: T, distance: u32) -> Self {
Self { distance, data }
}
pub fn one(data: T) -> Self { pub fn one(data: T) -> Self {
Self { distance: 1, data } Self { distance: 1, data }
} }
@ -478,111 +316,62 @@ impl<T: Eq> Ord for Distanced<T> {
} }
} }
impl PartialOrd for Relation { impl From<(&str, &str)> for NodeId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn from(value: (&str, &str)) -> Self {
self.0.partial_cmp(&other.0)
}
}
impl Ord for Relation {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for ObjectOrSet {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (
self.object().partial_cmp(other.object()),
self.relation(),
other.relation(),
) {
(Some(Ordering::Equal), self_rel, other_rel) => self_rel.partial_cmp(&other_rel),
(ord, _, _) => ord,
}
}
}
impl Ord for ObjectOrSet {
fn cmp(&self, other: &Self) -> Ordering {
self.object()
.cmp(other.object())
.then(self.relation().cmp(&other.relation()))
}
}
impl PartialOrd for Object {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.namespace.partial_cmp(&other.namespace) {
Some(core::cmp::Ordering::Equal) => self.id.partial_cmp(&other.id),
ord => ord,
}
}
}
impl Ord for Object {
fn cmp(&self, other: &Self) -> Ordering {
self.namespace
.cmp(&other.namespace)
.then(self.id.cmp(&other.id))
}
}
impl From<(&str, &str)> for ObjectOrSet {
fn from((namespace, id): (&str, &str)) -> Self {
ObjectOrSet::Object(Object {
namespace: namespace.into(),
id: id.into(),
})
}
}
impl From<(&str, &str, &str)> for ObjectOrSet {
fn from((namespace, id, rel): (&str, &str, &str)) -> Self {
ObjectOrSet::Set(((namespace, id).into(), Relation(rel.into())))
}
}
impl From<(&str, &str)> for Object {
fn from((namespace, id): (&str, &str)) -> Self {
Self { Self {
namespace: namespace.into(), namespace: value.0.to_string(),
id: id.into(), id: value.1.to_string(),
relation: None,
} }
} }
} }
impl From<(String, String)> for Object {
fn from((namespace, id): (String, String)) -> Self { impl From<(&str, &str, &str)> for NodeId {
fn from(value: (&str, &str, &str)) -> Self {
Self { Self {
namespace: namespace.into(), namespace: value.0.to_string(),
id: id.into(), id: value.1.to_string(),
relation: Some(value.2.to_string()),
} }
} }
} }
impl From<&str> for Relation { impl From<(&str, &str, Option<&str>)> for NodeId {
fn from(value: &str) -> Self { fn from(value: (&str, &str, Option<&str>)) -> Self {
Relation(value.into()) Self {
} namespace: value.0.to_string(),
} id: value.1.to_string(),
impl From<String> for Relation { relation: value.2.map(|x| x.to_string()),
fn from(value: String) -> Self { }
Relation(value.into())
} }
} }
impl ObjectOrSet { impl From<(String, String)> for NodeId {
pub fn object(&self) -> &Object { fn from(value: (String, String)) -> Self {
match self { Self {
ObjectOrSet::Object(obj) => obj, namespace: value.0,
ObjectOrSet::Set((obj, _)) => obj, id: value.1,
} relation: None,
}
pub fn relation(&self) -> Option<&Relation> {
match self {
ObjectOrSet::Object(_) => None,
ObjectOrSet::Set((_, rel)) => Some(rel),
} }
} }
} }
impl Relation {
pub fn new(relation: &str) -> Self { impl From<(String, String, String)> for NodeId {
Self(relation.into()) fn from(value: (String, String, String)) -> Self {
Self {
namespace: value.0,
id: value.1,
relation: Some(value.2),
}
}
}
impl From<(String, String, Option<String>)> for NodeId {
fn from(value: (String, String, Option<String>)) -> Self {
Self {
namespace: value.0,
id: value.1,
relation: value.2,
}
} }
} }