mirror of
https://github.com/pfzetto/rebacs
synced 2024-11-23 19:52:50 +01:00
openid connect integration
This commit is contained in:
parent
faf4e82f88
commit
66fac9d185
8 changed files with 1304 additions and 1257 deletions
990
Cargo.lock
generated
990
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -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
129
flake.lock
Normal 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
77
flake.nix
Normal 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 ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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", ¤t.0, ¤t.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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue