summaryrefslogtreecommitdiff
path: root/src/authorization
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-10 12:52:39 -0600
committermo khan <mo@mokhan.ca>2025-07-10 12:52:39 -0600
commit7f253078df95ea0ec725ccbd000f11723697b64d (patch)
tree388e9f1298ccf394cfff32e4ad5a4f8090aa7fd4 /src/authorization
parentffed217e230f6c6e725383c00900c5d5b4981f6c (diff)
feat: hack together a CLI to generate an entitites.json file
Diffstat (limited to 'src/authorization')
-rw-r--r--src/authorization/entities.rs267
-rw-r--r--src/authorization/mod.rs2
2 files changed, 269 insertions, 0 deletions
diff --git a/src/authorization/entities.rs b/src/authorization/entities.rs
new file mode 100644
index 00000000..7af5fa4a
--- /dev/null
+++ b/src/authorization/entities.rs
@@ -0,0 +1,267 @@
+use serde::Serialize;
+use std::collections::HashSet;
+use std::fs;
+
+// Cedar entity structures
+#[derive(Debug, Serialize)]
+pub struct CedarEntity {
+ pub uid: CedarUid,
+ pub attrs: serde_json::Value,
+ pub parents: Vec<CedarParent>,
+}
+
+#[derive(Debug, Serialize)]
+pub struct CedarUid {
+ #[serde(rename = "type")]
+ pub entity_type: String,
+ pub id: String,
+}
+
+#[derive(Debug, Serialize)]
+pub struct CedarParent {
+ #[serde(rename = "type")]
+ pub parent_type: String,
+ pub id: String,
+}
+
+// API structures
+#[derive(Debug, serde::Deserialize)]
+pub struct Project {
+ pub id: u64,
+ pub name: String,
+ pub path: String,
+ pub namespace: Namespace,
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct Namespace {
+ pub id: u64,
+ pub name: String,
+ pub path: String,
+ pub kind: String,
+ pub full_path: String,
+ pub parent_id: Option<u64>,
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct Member {
+ pub id: u64,
+ pub username: String,
+ pub name: String,
+ pub state: String,
+ pub access_level: u8,
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct Group {
+ pub id: u64,
+ pub name: String,
+ pub path: String,
+ pub full_path: String,
+ pub parent_id: Option<u64>,
+}
+
+pub async fn generate_entities_from_api(
+ token: String,
+ host: String,
+ project: String,
+) -> Result<Vec<CedarEntity>, Box<dyn std::error::Error>> {
+ let client = reqwest::Client::new();
+ let mut entities = Vec::new();
+ let mut processed_groups = HashSet::new();
+
+ // Fetch project information
+ let project_url = format!(
+ "{}/api/v4/projects/{}",
+ host.trim_end_matches('/'),
+ urlencoding::encode(&project)
+ );
+
+ println!("Fetching project information...");
+ let project: Project = client
+ .get(&project_url)
+ .header("PRIVATE-TOKEN", &token)
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?;
+
+ // Add organization
+ entities.push(CedarEntity {
+ uid: CedarUid {
+ entity_type: "Organization".to_string(),
+ id: "1".to_string(),
+ },
+ attrs: serde_json::json!({
+ "name": "gitlab",
+ }),
+ parents: vec![],
+ });
+
+ // Add project entity
+ entities.push(CedarEntity {
+ uid: CedarUid {
+ entity_type: "Project".to_string(),
+ id: project.id.to_string(),
+ },
+ attrs: serde_json::json!({
+ "name": project.name,
+ "path": project.path,
+ "full_path": format!("{}/{}", project.namespace.full_path, project.path),
+ }),
+ parents: if project.namespace.kind == "group" {
+ vec![CedarParent {
+ parent_type: "Group".to_string(),
+ id: project.namespace.id.to_string(),
+ }]
+ } else {
+ vec![]
+ },
+ });
+
+ // Fetch project members
+ let members_url = format!(
+ "{}/api/v4/projects/{}/members/all",
+ host.trim_end_matches('/'),
+ project.id
+ );
+
+ println!("Fetching project members...");
+ let members: Vec<Member> = client
+ .get(&members_url)
+ .header("PRIVATE-TOKEN", &token)
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?;
+
+ println!("Found {} members", members.len());
+
+ // Add user entities
+ for member in members {
+ if member.state == "active" {
+ entities.push(CedarEntity {
+ uid: CedarUid {
+ entity_type: "User".to_string(),
+ id: member.id.to_string(),
+ },
+ attrs: serde_json::json!({
+ "username": member.username,
+ "name": member.name,
+ "access_level": member.access_level,
+ }),
+ parents: vec![],
+ });
+ }
+ }
+
+ // Fetch group hierarchy if project belongs to a group
+ if project.namespace.kind == "group" {
+ println!("Fetching group hierarchy...");
+ fetch_group_hierarchy(
+ &client,
+ &host,
+ &token,
+ project.namespace.id,
+ &mut entities,
+ &mut processed_groups,
+ )
+ .await?;
+ }
+
+ Ok(entities)
+}
+
+pub fn fetch_group_hierarchy<'a>(
+ client: &'a reqwest::Client,
+ api_url: &'a str,
+ token: &'a str,
+ group_id: u64,
+ entities: &'a mut Vec<CedarEntity>,
+ processed_groups: &'a mut HashSet<u64>,
+) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error>>> + 'a>>
+{
+ Box::pin(async move {
+ if processed_groups.contains(&group_id) {
+ return Ok(());
+ }
+
+ processed_groups.insert(group_id);
+
+ let group_url = format!(
+ "{}/api/v4/groups/{}",
+ api_url.trim_end_matches('/'),
+ group_id
+ );
+
+ let group: Group = client
+ .get(&group_url)
+ .header("PRIVATE-TOKEN", token)
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?;
+
+ let parents = if let Some(parent_id) = group.parent_id {
+ // Recursively fetch parent group
+ fetch_group_hierarchy(
+ client,
+ api_url,
+ token,
+ parent_id,
+ entities,
+ processed_groups,
+ )
+ .await?;
+ vec![CedarParent {
+ parent_type: "Group".to_string(),
+ id: parent_id.to_string(),
+ }]
+ } else {
+ // Top-level group belongs to organization
+ vec![CedarParent {
+ parent_type: "Organization".to_string(),
+ id: "1".to_string(),
+ }]
+ };
+
+ entities.push(CedarEntity {
+ uid: CedarUid {
+ entity_type: "Group".to_string(),
+ id: group.id.to_string(),
+ },
+ attrs: serde_json::json!({
+ "name": group.name,
+ "path": group.path,
+ "full_path": group.full_path,
+ }),
+ parents,
+ });
+
+ Ok(())
+ })
+}
+
+pub fn write_entities_file(
+ entities: &[CedarEntity],
+ output: &str,
+) -> Result<(), Box<dyn std::error::Error>> {
+ let json = serde_json::to_string_pretty(&entities)?;
+ fs::write(output, json)?;
+
+ println!(
+ "\nSuccessfully wrote {} entities to {}",
+ entities.len(),
+ output
+ );
+ println!("\nTo use these entities with Cedar:");
+ println!(
+ " let entities = cedar_policy::Entities::from_json_file(\"{}\", None)?;",
+ output
+ );
+
+ Ok(())
+}
diff --git a/src/authorization/mod.rs b/src/authorization/mod.rs
index d664815b..7e1d69b5 100644
--- a/src/authorization/mod.rs
+++ b/src/authorization/mod.rs
@@ -1,9 +1,11 @@
pub mod authorizer;
pub mod cedar_authorizer;
pub mod check_service;
+pub mod entities;
pub mod server;
pub use authorizer::Authorizer;
pub use cedar_authorizer::CedarAuthorizer;
pub use check_service::CheckService;
+pub use entities::{CedarEntity, generate_entities_from_api, write_entities_file};
pub use server::Server;