tandem-server 0.6.5

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, 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 Some(grant) = grants.values().find(|grant| {
                enterprise_delegation_grant_matches_scope(
                    grant,
                    &tenant_context,
                    &scope,
                    grant_id,
                    now_ms,
                )
            }) else {
                anyhow::bail!(
                    "delegation grant `{grant_id}` is not active authority for automation `{}`",
                    automation.automation_id
                );
            };
            if !grant.permissions.contains(&AccessPermission::Execute) {
                anyhow::bail!(
                    "delegation grant `{}` does not include execute authority for automation `{}`",
                    grant.grant_id,
                    automation.automation_id
                );
            }
        }
        Ok(())
    }
}

fn enterprise_delegation_grant_matches_scope(
    grant: &OrganizationUnitAccessGrant,
    tenant_context: &TenantContext,
    scope: &AutomationEnterpriseScope,
    grant_id: &str,
    now_ms: u64,
) -> bool {
    grant.grant_id.trim() == grant_id.trim()
        && 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.effect == AccessEffect::Allow
        && 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))
}