tandem-server 0.6.9

HTTP server for Tandem engine APIs
use crate::automation_v2::types::{AutomationEnterpriseScope, AutomationV2Spec};
use crate::util::time::now_ms;
use tandem_enterprise_contract::{
    canonical_enterprise_scope_id, AccessEffect, OrganizationUnitAccessGrant,
};
use tandem_types::{AccessPermission, PrincipalRef, TenantContext};

use super::AppState;

impl AppState {
    pub(crate) async fn validate_automation_enterprise_delegation_grants(
        &self,
        automation: &AutomationV2Spec,
    ) -> anyhow::Result<()> {
        let Some(scope) = automation.enterprise_scope() else {
            return Ok(());
        };
        if scope.delegation_grant_ids.is_empty() {
            return Ok(());
        }
        let tenant_context = automation.tenant_context();
        let grants = self.enterprise.org_unit_access_grants.read().await;
        let now_ms = now_ms();
        for grant_id in &scope.delegation_grant_ids {
            let matching = grants.values().filter(|grant| {
                enterprise_delegation_grant_matches_scope(
                    grant,
                    &tenant_context,
                    &scope,
                    grant_id,
                    now_ms,
                )
            });
            validate_matching_delegation_grants(
                matching,
                grant_id,
                &automation.automation_id,
                AccessPermission::Execute,
            )?;
        }
        Ok(())
    }

    pub(crate) async fn validate_automation_enterprise_delegation_grants_for_author(
        &self,
        automation: &AutomationV2Spec,
        author: &PrincipalRef,
    ) -> anyhow::Result<()> {
        self.validate_automation_enterprise_delegation_grants(automation)
            .await?;

        let Some(scope) = automation.enterprise_scope() else {
            return Ok(());
        };
        if scope.delegation_grant_ids.is_empty() {
            return Ok(());
        }

        let tenant_context = automation.tenant_context();
        let graph = self
            .build_intra_tenant_authority_graph(&tenant_context, Vec::new())
            .await;
        let now_ms = now_ms();
        let author_units = graph.resolved_unit_principals(author, now_ms);

        for grant_id in &scope.delegation_grant_ids {
            for permission in [AccessPermission::View, AccessPermission::Execute] {
                let matching = graph.unit_access_grants.iter().filter(|grant| {
                    author_units.contains(&grant.unit)
                        && enterprise_delegation_grant_matches_scope(
                            grant,
                            &tenant_context,
                            &scope,
                            grant_id,
                            now_ms,
                        )
                });
                validate_matching_delegation_grants(
                    matching,
                    grant_id,
                    &automation.automation_id,
                    permission,
                )
                .map_err(|error| {
                    anyhow::anyhow!(
                        "author `{}` cannot inspect/use automation `{}`: {error}",
                        author.id,
                        automation.automation_id
                    )
                })?;
            }
        }
        Ok(())
    }
}

fn validate_matching_delegation_grants<'a>(
    grants: impl Iterator<Item = &'a OrganizationUnitAccessGrant>,
    grant_id: &str,
    automation_id: &str,
    permission: AccessPermission,
) -> anyhow::Result<()> {
    let matching = grants.collect::<Vec<_>>();
    if matching
        .iter()
        .any(|grant| grant.effect == AccessEffect::Deny && grant.permissions.contains(&permission))
    {
        anyhow::bail!(
            "delegation grant `{grant_id}` explicitly denies {permission:?} authority for automation `{automation_id}`"
        );
    }
    match matching.iter().find(|grant| {
        grant.effect == AccessEffect::Allow && grant.permissions.contains(&permission)
    }) {
        Some(_) => Ok(()),
        None if matching.is_empty() => {
            anyhow::bail!(
                "delegation grant `{grant_id}` is not active authority for automation `{automation_id}`"
            );
        }
        None => anyhow::bail!(
            "delegation grant `{grant_id}` does not include {permission:?} authority for automation `{automation_id}`"
        ),
    }
}

fn enterprise_delegation_grant_matches_scope(
    grant: &OrganizationUnitAccessGrant,
    tenant_context: &TenantContext,
    scope: &AutomationEnterpriseScope,
    grant_id: &str,
    now_ms: u64,
) -> bool {
    canonical_enterprise_scope_id(&grant.grant_id) == canonical_enterprise_scope_id(grant_id)
        && grant.tenant_context.org_id == tenant_context.org_id
        && grant.tenant_context.workspace_id == tenant_context.workspace_id
        && grant.tenant_context.deployment_id == tenant_context.deployment_id
        && grant.is_active_at(now_ms)
        && enterprise_delegation_grant_org_unit_matches(grant, scope)
        && enterprise_delegation_grant_resource_matches(grant, scope)
        && enterprise_delegation_grant_data_classes_match(grant, scope)
}

fn enterprise_delegation_grant_org_unit_matches(
    grant: &OrganizationUnitAccessGrant,
    scope: &AutomationEnterpriseScope,
) -> bool {
    let Some(expected) = scope.owning_org_unit_id.as_deref() else {
        return true;
    };
    let Some(expected) = canonical_enterprise_scope_id(expected) else {
        return false;
    };
    let Some(actual) = canonical_enterprise_scope_id(&grant.unit.id) else {
        return false;
    };
    actual == expected || actual.ends_with(&format!("/{expected}"))
}

fn enterprise_delegation_grant_resource_matches(
    grant: &OrganizationUnitAccessGrant,
    scope: &AutomationEnterpriseScope,
) -> bool {
    let Some(resource_scope) = scope.resource_scope.as_ref() else {
        return true;
    };
    grant.resource.applies_to(&resource_scope.root)
        && resource_scope
            .allowed_resources
            .iter()
            .all(|resource| grant.resource.applies_to(resource))
}

fn enterprise_delegation_grant_data_classes_match(
    grant: &OrganizationUnitAccessGrant,
    scope: &AutomationEnterpriseScope,
) -> bool {
    scope.data_classes.is_empty()
        || scope
            .data_classes
            .iter()
            .all(|data_class| grant.data_classes.contains(data_class))
}