use cedarling::{
AuthorizationConfig, BootstrapConfig, Cedarling, DataStoreConfig, EntityData, HttpClientConfig,
InitCedarlingError, JwtConfig, LogConfig, LogLevel, LogTypeConfig, PolicyStoreConfig,
PolicyStoreSource, RequestUnsigned,
};
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use serde::Deserialize;
use serde_json::json;
use std::hint::black_box;
use std::time::Duration;
use tokio::runtime::Runtime;
const POLICY_STORE: &str = r#"
cedar_version: v4.0.0
policy_stores:
bench_store:
cedar_version: v4.0.0
name: "Benchmark"
description: "Policy store for authorize_unsigned benchmarks"
policies:
allow_user_update:
description: "Allow update when country matches and role is Admin"
policy_content:
encoding: none
content_type: cedar
body: |-
permit(
principal is Jans::User,
action in [Jans::Action::"Update"],
resource is Jans::Issue
) when {
principal.country == resource.country &&
principal in Jans::Role::"Admin" &&
principal.department == "engineering"
};
allow_user_read:
description: "Allow read for any authenticated user"
policy_content:
encoding: none
content_type: cedar
body: |-
permit(
principal is Jans::User,
action in [Jans::Action::"Read"],
resource is Jans::Issue
) when {
principal.active == true
};
deny_suspended:
description: "Deny if user is suspended"
policy_content:
encoding: none
content_type: cedar
body: |-
forbid(
principal is Jans::User,
action,
resource
) when {
principal.suspended == true
};
schema:
encoding: none
content_type: cedar
body: |-
namespace Jans {
entity Issue = {
"country": String,
"org_id": String,
"priority": Long,
};
entity Role;
entity User in [Role] = {
"country": String,
"department": String,
"active": Bool,
"suspended": Bool,
};
action "Update" appliesTo {
principal: [User, Role],
resource: [Issue],
context: {}
};
action "Read" appliesTo {
principal: [User, Role],
resource: [Issue],
context: {}
};
}
"#;
async fn validate_cedarling_works(cedarling: &Cedarling, request: &RequestUnsigned) {
let result = cedarling
.authorize_unsigned(request.clone())
.await
.expect("authorization call failed");
assert!(result.decision, "expected allow for benchmark validation");
}
fn authorize_unsigned_benchmark(c: &mut Criterion) {
let runtime = Runtime::new().expect("init tokio runtime");
let cedarling = runtime
.block_on(prepare_cedarling())
.expect("should initialize Cedarling");
let request = prepare_unsigned_request();
runtime.block_on(validate_cedarling_works(&cedarling, &request));
c.bench_with_input(
BenchmarkId::new("authz_authorize_unsigned", "tokio runtime"),
&runtime,
|b, rt| {
b.to_async(rt)
.iter(|| cedarling.authorize_unsigned(black_box(request.clone())));
},
);
}
fn measurement_config() -> Criterion {
Criterion::default()
.measurement_time(Duration::from_secs(5))
.warm_up_time(Duration::from_secs(5))
}
criterion_group! {
name = authz_benchmark;
config = measurement_config();
targets = authorize_unsigned_benchmark
}
criterion_main!(authz_benchmark);
async fn prepare_cedarling() -> Result<Cedarling, InitCedarlingError> {
let bootstrap_config = BootstrapConfig {
application_name: "test_app".to_string(),
log_config: LogConfig {
log_type: LogTypeConfig::Off,
log_level: LogLevel::DEBUG,
},
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Yaml(POLICY_STORE.to_string()),
..Default::default()
},
jwt_config: JwtConfig::new_without_validation(),
authorization_config: AuthorizationConfig::default(),
lock_config: None,
max_base64_size: None,
max_default_entities: None,
data_store_config: DataStoreConfig::default(),
http_client_config: HttpClientConfig::default(),
};
Cedarling::new(&bootstrap_config).await
}
#[must_use]
fn prepare_unsigned_request() -> RequestUnsigned {
let principal = EntityData::deserialize(json!({
"cedar_entity_mapping": {
"entity_type": "Jans::User",
"id": "boG8dfc5MKTn37o7gsdCeyqL8LpWQtgoO41m1KZwdq0"
},
"country": "US",
"department": "engineering",
"active": true,
"suspended": false
}))
.expect("valid principal entity");
let resource = EntityData::deserialize(json!({
"cedar_entity_mapping": {
"entity_type": "Jans::Issue",
"id": "random_id"
},
"org_id": "some_long_id",
"country": "US",
"priority": 1
}))
.expect("valid resource entity");
RequestUnsigned {
principal: Some(principal),
action: "Jans::Action::\"Read\"".to_string(),
context: json!({}),
resource,
}
}