Skip to content

Commit 6150a7c

Browse files
authored
Merge pull request #12 from totvscloud-seginf/main
Load entities and policies from json file
2 parents 8ed1788 + 3f44031 commit 6150a7c

File tree

9 files changed

+230
-1
lines changed

9 files changed

+230
-1
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ Cedar Agent configuration is available using environment variables and command l
9595
- The log level to filter logs. Defaults to `info`.
9696
`LOG_LEVEL` environment variable.
9797
`--log-level`, `-l` command line argument.
98+
- Load data from json file. Defaults to `None`.
99+
`DATA` environment variable.
100+
`--data`, `-d` command line argument.
101+
- Load policies from json file. Defaults to `None`.
102+
`POLICIES` environment variable.
103+
`--policies` command line argument.
98104

99105
**command line arguments take precedence over environment variables when configuring the Cedar Agent**
100106

src/config.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use fmt::Debug;
22
use std::borrow::Borrow;
33
use std::fmt;
4+
use std::path::PathBuf;
45

56
use clap::Parser;
67
use log::LevelFilter;
@@ -18,6 +19,10 @@ pub struct Config {
1819
pub port: Option<u16>,
1920
#[arg(short, long, value_enum)]
2021
pub log_level: Option<LevelFilter>,
22+
#[arg(short, long)]
23+
pub data: Option<PathBuf>,
24+
#[arg(long)]
25+
pub policies: Option<PathBuf>,
2126
}
2227

2328
impl Into<rocket::figment::Figment> for &Config {
@@ -34,6 +39,12 @@ impl Into<rocket::figment::Figment> for &Config {
3439
} else {
3540
config = config.merge(("port", 8180))
3641
}
42+
if let Some(data) = self.data.borrow() {
43+
config = config.merge(("data", data));
44+
}
45+
if let Some(policies) = self.policies.borrow() {
46+
config = config.merge(("policies", policies));
47+
}
3748

3849
config
3950
}
@@ -46,6 +57,8 @@ impl Config {
4657
addr: None,
4758
port: None,
4859
log_level: None,
60+
data: None,
61+
policies: None,
4962
}
5063
}
5164

@@ -56,6 +69,8 @@ impl Config {
5669
config.addr = c.addr.or(config.addr);
5770
config.port = c.port.or(config.port);
5871
config.log_level = c.log_level.or(config.log_level);
72+
config.data = c.data.or(config.data);
73+
config.policies = c.policies.or(config.policies);
5974
}
6075

6176
config

src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ async fn main() {
2929
let server_config: rocket::figment::Figment = config.borrow().into();
3030
let launch_result = rocket::custom(server_config)
3131
.attach(common::DefaultContentType::new(ContentType::JSON))
32+
.attach(services::data::load_from_file::InitDataFairing)
33+
.attach(services::policies::load_from_file::InitPoliciesFairing)
3234
.manage(config)
3335
.manage(Box::new(MemoryPolicyStore::new()) as Box<dyn PolicyStore>)
3436
.manage(Box::new(MemoryDataStore::new()) as Box<dyn DataStore>)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use std::path::PathBuf;
2+
use std::error::Error;
3+
use std::fs::File;
4+
use std::io::Read;
5+
use log::{error, info};
6+
7+
use rocket::fairing::{Fairing, Info, Kind};
8+
use rocket::Rocket;
9+
use rocket::Build;
10+
11+
use crate::services::data::DataStore;
12+
use crate::config;
13+
use crate::schemas::data::Entities;
14+
15+
pub struct InitDataFairing;
16+
17+
pub(crate) async fn init(conf: &config::Config, data_store: &Box<dyn DataStore>) {
18+
19+
if conf.data.is_none() {
20+
return;
21+
}
22+
23+
let file_path = conf.data.clone().unwrap();
24+
let entities_file_path = &file_path;
25+
let entities = match load_entities_from_file(entities_file_path.to_path_buf()).await {
26+
Ok(entities) => entities,
27+
Err(err) => {
28+
error!("Failed to load entities from file: {}", err);
29+
return;
30+
}
31+
};
32+
33+
match data_store.update_entities(entities).await {
34+
Ok(entities) => {
35+
info!("Successfully updated entities from file {}: {} entities", &file_path.display(), entities.len());
36+
}
37+
Err(err) => {
38+
error!("Failed to update entities: {}", err);
39+
return;
40+
}
41+
};
42+
}
43+
44+
pub async fn load_entities_from_file(path: PathBuf) -> Result<Entities, Box<dyn Error>> {
45+
46+
if !path.try_exists().unwrap_or(false) || !path.is_file() {
47+
return Err("File does not exist".into());
48+
}
49+
50+
if path.extension().unwrap() != "json" {
51+
return Err("File is not a json file".into());
52+
}
53+
54+
let mut file = match File::open(&path) {
55+
Ok(file) => file,
56+
Err(err) => return Err(format!("Failed to open file: {}", err).into()),
57+
};
58+
59+
let mut contents = String::new();
60+
if let Err(err) = file.read_to_string(&mut contents) {
61+
return Err(format!("Failed to read file: {}", err).into());
62+
}
63+
64+
let entities: Entities = match rocket::serde::json::from_str(&contents) {
65+
Ok(entities) => entities,
66+
Err(err) => return Err(format!("Failed to deserialize JSON: {}", err).into()),
67+
};
68+
69+
Ok(entities)
70+
}
71+
72+
#[async_trait::async_trait]
73+
impl Fairing for InitDataFairing {
74+
async fn on_ignite(&self, rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
75+
let config = rocket.state::<config::Config>();
76+
77+
if config.is_none() {
78+
return Ok(rocket);
79+
}
80+
81+
init(config.unwrap(), rocket.state::<Box<dyn DataStore>>().unwrap()).await;
82+
83+
Ok(rocket)
84+
}
85+
86+
fn info(&self) -> Info {
87+
Info {
88+
name: "Init Data",
89+
kind: Kind::Ignite
90+
}
91+
}
92+
}

src/services/data/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use async_trait::async_trait;
55
use crate::schemas::data as schemas;
66

77
pub mod memory;
8+
pub mod load_from_file;
89

910
#[async_trait]
1011
pub trait DataStore: Send + Sync {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::path::PathBuf;
2+
use std::error::Error;
3+
use std::fs::File;
4+
use std::io::Read;
5+
use log::{error, info};
6+
7+
use rocket::fairing::{Fairing, Info, Kind};
8+
use rocket::serde::json::Json;
9+
use rocket::Rocket;
10+
use rocket::Build;
11+
12+
use crate::services::policies::PolicyStore;
13+
use crate::schemas::policies::Policy;
14+
use crate::config;
15+
16+
pub struct InitPoliciesFairing;
17+
18+
pub(crate) async fn init(conf: &config::Config, policy_store: &Box<dyn PolicyStore>) {
19+
20+
if conf.policies.is_none() {
21+
return;
22+
}
23+
24+
let file_path = conf.policies.clone().unwrap();
25+
let policies_file_path = &file_path;
26+
let policies = match load_policies_from_file(policies_file_path.to_path_buf()).await {
27+
Ok(policies) => policies,
28+
Err(err) => {
29+
error!("Failed to load policies from file: {}", err);
30+
return;
31+
}
32+
};
33+
34+
match policy_store.update_policies(policies.into_inner()).await {
35+
Ok(policies) => {
36+
info!("Successfully updated policies from file {}: {} policies", &file_path.display(), policies.len());
37+
}
38+
Err(err) => {
39+
error!("Failed to update policies: {}", err);
40+
return;
41+
}
42+
};
43+
}
44+
45+
pub async fn load_policies_from_file(path: PathBuf) -> Result<Json<Vec<Policy>>, Box<dyn Error>> {
46+
47+
if !path.try_exists().unwrap_or(false) || !path.is_file() {
48+
return Err("File does not exist".into());
49+
}
50+
51+
if path.extension().unwrap() != "json" {
52+
return Err("File is not a json file".into());
53+
}
54+
55+
let mut file = match File::open(&path) {
56+
Ok(file) => file,
57+
Err(err) => return Err(format!("Failed to open file: {}", err).into()),
58+
};
59+
60+
let mut contents = String::new();
61+
if let Err(err) = file.read_to_string(&mut contents) {
62+
return Err(format!("Failed to read file: {}", err).into());
63+
}
64+
65+
let policies: Vec<Policy> = match rocket::serde::json::from_str(&contents) {
66+
Ok(policies) => policies,
67+
Err(err) => return Err(format!("Failed to deserialize JSON: {}", err).into()),
68+
};
69+
70+
Ok(Json(policies))
71+
}
72+
73+
#[async_trait::async_trait]
74+
impl Fairing for InitPoliciesFairing {
75+
async fn on_ignite(&self, rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
76+
let config = rocket.state::<config::Config>();
77+
78+
if config.is_none() {
79+
return Ok(rocket);
80+
}
81+
82+
init(config.unwrap(), rocket.state::<Box<dyn PolicyStore>>().unwrap()).await;
83+
84+
Ok(rocket)
85+
}
86+
87+
fn info(&self) -> Info {
88+
Info {
89+
name: "Init Policies",
90+
kind: Kind::Ignite
91+
}
92+
}
93+
}

src/services/policies/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::schemas::policies::{Policy, PolicyUpdate};
77

88
pub(crate) mod errors;
99
pub mod memory;
10+
pub mod load_from_file;
1011

1112
#[async_trait]
1213
pub trait PolicyStore: Send + Sync {

tests/services/data_tests.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use std::path::PathBuf;
2+
13
use crate::services::utils;
2-
use cedar_agent::data::memory::MemoryDataStore;
34

5+
use cedar_agent::data::memory::MemoryDataStore;
6+
use cedar_agent::data::load_from_file::load_entities_from_file;
47
use cedar_agent::DataStore;
58

69
#[tokio::test]
@@ -18,3 +21,9 @@ async fn memory_tests() {
1821
let entities = store.get_entities().await;
1922
assert_eq!(entities.len(), 0);
2023
}
24+
25+
#[tokio::test]
26+
async fn test_load_entities_from_file() {
27+
let entities = load_entities_from_file(PathBuf::from("./examples/data.json")).await.unwrap();
28+
assert_eq!(entities.len(), 12);
29+
}

tests/services/policies_tests.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use std::str::FromStr;
2+
use std::path::PathBuf;
23

34
use cedar_policy::PolicyId;
45

56
use crate::services::utils::*;
7+
68
use cedar_agent::policies::memory::MemoryPolicyStore;
79
use cedar_agent::schemas::policies::PolicyUpdate;
810
use cedar_agent::PolicyStore;
11+
use cedar_agent::policies::load_from_file::load_policies_from_file;
912

1013
#[tokio::test]
1114
async fn memory_tests() {
@@ -82,3 +85,10 @@ async fn memory_tests() {
8285
.policy(&PolicyId::from_str("test").unwrap())
8386
.is_none());
8487
}
88+
89+
#[tokio::test]
90+
async fn test_load_policies_from_file() {
91+
let policies = load_policies_from_file(PathBuf::from("./examples/policies.json")).await.unwrap();
92+
assert_eq!(policies.len(), 3);
93+
assert_eq!(policies[0].id, "admins-policy".to_string());
94+
}

0 commit comments

Comments
 (0)