Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SchemaStore and policy validation #24

Merged
merged 11 commits into from
May 26, 2024
Merged
84 changes: 84 additions & 0 deletions examples/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"": {
"entityTypes": {
"User": {
"shape": {
"type": "Record",
"attributes": {}
},
"memberOfTypes": [
"Role"
]
},
"Role": {
"shape": {
"type": "Record",
"attributes": {}
}
},
"Document": {
"shape": {
"type": "Record",
"attributes": {}
}
}
},
"actions": {
"create": {
"appliesTo": {
"principalTypes": [
"User",
"Role"
],
"resourceTypes": [
"Document"
]
}
},
"delete": {
"appliesTo": {
"principalTypes": [
"User",
"Role"
],
"resourceTypes": [
"Document"
]
}
},
"get": {
"appliesTo": {
"principalTypes": [
"User",
"Role"
],
"resourceTypes": [
"Document"
]
}
},
"list": {
"appliesTo": {
"principalTypes": [
"User",
"Role"
],
"resourceTypes": [
"Document"
]
}
},
"update": {
"appliesTo": {
"principalTypes": [
"User",
"Role"
],
"resourceTypes": [
"Document"
]
}
}
}
}
}
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct Config {
pub data: Option<PathBuf>,
#[arg(long)]
pub policies: Option<PathBuf>,
#[arg(short, long)]
pub schema: Option<PathBuf>,
}

impl Into<rocket::figment::Figment> for &Config {
Expand All @@ -45,6 +47,9 @@ impl Into<rocket::figment::Figment> for &Config {
if let Some(policies) = self.policies.borrow() {
config = config.merge(("policies", policies));
}
if let Some(schema) = self.schema.borrow() {
config = config.merge(("schema", schema));
}

config
}
Expand All @@ -59,6 +64,7 @@ impl Config {
log_level: None,
data: None,
policies: None,
schema: None
}
}

Expand All @@ -71,6 +77,7 @@ impl Config {
config.log_level = c.log_level.or(config.log_level);
config.data = c.data.or(config.data);
config.policies = c.policies.or(config.policies);
config.schema = c.schema.or(config.schema);
}

config
Expand Down
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::services::data::memory::MemoryDataStore;
use crate::services::data::DataStore;
use crate::services::policies::memory::MemoryPolicyStore;
use crate::services::policies::PolicyStore;
use crate::services::schema::memory::MemorySchemaStore;
use crate::services::schema::SchemaStore;

mod authn;
mod common;
Expand All @@ -36,6 +38,7 @@ async fn main() -> ExitCode {
.manage(config)
.manage(Box::new(MemoryPolicyStore::new()) as Box<dyn PolicyStore>)
.manage(Box::new(MemoryDataStore::new()) as Box<dyn DataStore>)
.manage(Box::new(MemorySchemaStore::new()) as Box<dyn SchemaStore>)
.manage(cedar_policy::Authorizer::new())
.register(
"/",
Expand All @@ -59,6 +62,9 @@ async fn main() -> ExitCode {
routes::data::update_entities,
routes::data::delete_entities,
routes::authorization::is_authorized,
routes::schema::get_schema,
routes::schema::update_schema,
routes::schema::delete_schema
],
)
.mount(
Expand Down
1 change: 1 addition & 0 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rocket_okapi::openapi;
pub mod authorization;
pub mod data;
pub mod policies;
pub mod schema;

#[openapi]
#[get("/")]
Expand Down
40 changes: 37 additions & 3 deletions src/routes/policies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use rocket_okapi::openapi;
use crate::authn::ApiKey;
use crate::errors::response::AgentError;
use crate::schemas::policies as schemas;
use crate::services::policies::errors::PolicyStoreError;
use crate::services::policies::PolicyStore;
use crate::services::schema::SchemaStore;

#[openapi]
#[get("/policies")]
Expand Down Expand Up @@ -41,11 +43,21 @@ pub async fn create_policy(
_auth: ApiKey,
policy: Json<schemas::Policy>,
policy_store: &State<Box<dyn PolicyStore>>,
schema_store: &State<Box<dyn SchemaStore>>,
) -> Result<Json<schemas::Policy>, AgentError> {
let policy = policy.into_inner();
let added_policy = policy_store.create_policy(policy.borrow()).await;
let schema =
if schema_store.schema_empty().await
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved
{ None }
else
{ Some(schema_store.schema().await) };
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved

let added_policy = policy_store.create_policy(policy.borrow(), schema).await;
match added_policy {
Ok(p) => Ok(Json::from(p)),
Err(PolicyStoreError::PolicyInvalid(_, reason)) => Err(AgentError::BadRequest {
reason
}),
Err(_) => Err(AgentError::Duplicate {
id: policy.id,
object: "policy",
Expand All @@ -59,8 +71,18 @@ pub async fn update_policies(
_auth: ApiKey,
policy: Json<Vec<schemas::Policy>>,
policy_store: &State<Box<dyn PolicyStore>>,
schema_store: &State<Box<dyn SchemaStore>>,
) -> Result<Json<Vec<schemas::Policy>>, AgentError> {
let updated_policy = policy_store.update_policies(policy.into_inner()).await;
let schema =
if schema_store.schema_empty().await
{ None }
else
{ Some(schema_store.schema().await) };
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved

let updated_policy = policy_store.update_policies(
policy.into_inner(),
schema
).await;
match updated_policy {
Ok(p) => Ok(Json::from(p)),
Err(e) => Err(AgentError::BadRequest {
Expand All @@ -76,8 +98,20 @@ pub async fn update_policy(
id: String,
policy: Json<schemas::PolicyUpdate>,
policy_store: &State<Box<dyn PolicyStore>>,
schema_store: &State<Box<dyn SchemaStore>>,
) -> Result<Json<schemas::Policy>, AgentError> {
let updated_policy = policy_store.update_policy(id, policy.into_inner()).await;
let schema =
if schema_store.schema_empty().await
{ None }
else
{ Some(schema_store.schema().await) };
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved

let updated_policy = policy_store.update_policy(
id,
policy.into_inner(),
schema
).await;

match updated_policy {
Ok(p) => Ok(Json::from(p)),
Err(err) => Err(AgentError::BadRequest {
Expand Down
43 changes: 43 additions & 0 deletions src/routes/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use rocket::{delete, get, put, State};
use rocket::response::status;
use rocket::serde::json::Json;
use rocket_okapi::openapi;

use crate::authn::ApiKey;
use crate::errors::response::AgentError;
use crate::schemas::schema::Schema;
use crate::services::schema::SchemaStore;

#[openapi]
#[get("/schema")]
pub async fn get_schema(
_auth: ApiKey,
schema_store: &State<Box<dyn SchemaStore>>
) -> Result<Json<Schema>, AgentError> {
Ok(Json::from(schema_store.get_schema().await))
}

#[openapi]
#[put("/schema", format = "json", data = "<schema>")]
pub async fn update_schema(
_auth: ApiKey,
schema_store: &State<Box<dyn SchemaStore>>,
schema: Json<Schema>
) -> Result<Json<Schema>, AgentError> {
match schema_store.update_schema(schema.into_inner()).await {
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved
Ok(schema) => Ok(Json::from(schema)),
Err(err) => Err(AgentError::BadRequest {
reason: err.to_string(),
}),
}
}

#[openapi]
#[delete("/schema")]
pub async fn delete_schema(
_auth: ApiKey,
schema_store: &State<Box<dyn SchemaStore>>
) -> Result<status::NoContent, AgentError> {
schema_store.delete_schema().await;
Ok(status::NoContent)
}
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions src/schemas/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod authorization;
pub mod data;
pub mod policies;
pub mod schema;
32 changes: 32 additions & 0 deletions src/schemas/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use cedar_policy;
use log::debug;
use serde::{Deserialize, Serialize};

use rocket::serde::json::Value;
use rocket::serde::json::serde_json::Map;
use rocket_okapi::okapi::schemars;
use rocket_okapi::okapi::schemars::JsonSchema;

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Schema(Value);

impl Schema {
pub fn empty() -> Self {
Self {
0: Value::Object(Map::new())
}
}

pub fn is_empty(&self) -> bool {
self.0 == Value::Object(Map::new())
}
}

impl TryInto<cedar_policy::Schema> for Schema {
type Error = cedar_policy::SchemaError;

fn try_into(self) -> Result<cedar_policy::Schema, Self::Error> {
debug!("Parsing schema");
cedar_policy::Schema::from_json_value(self.0)
}
}
Akamatsu21 marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod data;
pub mod policies;
pub mod schema;

pub use data::DataStore;
pub use policies::PolicyStore;
pub use schema::SchemaStore;
6 changes: 6 additions & 0 deletions src/services/policies/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ pub enum PolicyStoreError {
/// Reference to PolicySetError.
#[error("Unable to modify the policies: {0}")]
PolicySetError(#[from] cedar_policy::PolicySetError),
/// Reference to ParseErrors.
#[error("Unable to parse policy: {0}")]
PolicyParseError(#[from] cedar_policy_core::parser::err::ParseErrors),
/// Policy with the given id was not found.
#[error("Unable to find policy with id {0}")]
PolicyNotFoundError(String),
/// Validation returned an error.
#[error("Failed validating policy {0} against the schema: {1}")]
PolicyInvalid(String, String)
}
Loading