diff --git a/Cargo.lock b/Cargo.lock index 1ebccc3..7ff8793 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1050,6 +1050,23 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpex" +version = "0.2.0" +dependencies = [ + "apl-audit-logger", + "apl-cmf", + "apl-core", + "apl-cpex", + "apl-delegator-oauth", + "apl-identity-jwt", + "apl-pdp-cedar-direct", + "apl-pdp-cel", + "apl-pii-scanner", + "apl-session-valkey", + "cpex-core", +] + [[package]] name = "cpex-core" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 01c6bc8..2c5fbdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ [workspace] resolver = "2" members = [ + "crates/cpex", "crates/cpex-core", "crates/cpex-orchestration", "crates/cpex-sdk", @@ -38,6 +39,7 @@ members = [ # cargo build -p apl-cedarling # just this one # cargo test --workspace # full sweep (CI) default-members = [ + "crates/cpex", "crates/cpex-core", "crates/cpex-orchestration", "crates/cpex-sdk", diff --git a/crates/cpex/Cargo.toml b/crates/cpex/Cargo.toml new file mode 100644 index 0000000..7139fec --- /dev/null +++ b/crates/cpex/Cargo.toml @@ -0,0 +1,61 @@ +# Location: ./crates/cpex/Cargo.toml +# Copyright 2025 +# SPDX-License-Identifier: Apache-2.0 +# Authors: Fred Araujo +# +# cpex — batteries-included host facade. +# +# One dependency instead of a dozen: re-exports the host runtime +# (PluginManager, AplOptions, register_apl) plus the bundled plugin +# factories, each gated behind a cargo feature. A host enables the +# plugins it wants and gets them through this single crate rather than +# pinning apl-cmf / apl-cpex / apl-pdp-* / apl-session-* individually. +# +# cpex = { version = "0.2.0", features = ["jwt", "oauth", "cedar", "cel", "valkey"] } +# +# then `cpex::install_builtins(&mgr)` registers every enabled factory and +# installs the APL config visitor in one call. + +[package] +name = "cpex" +description = "CPEX host facade — re-exports the runtime and feature-gated plugin factories." +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true + +[features] +# No plugins are forced on a re-export-only consumer, but the common +# in-process set is on by default so `cpex = "0.2"` is useful out of the +# box. Heavier / external-backend plugins (valkey, and future cedarling) +# stay opt-in, mirroring the workspace's default-members policy. +default = ["jwt", "oauth", "pii", "audit", "cedar", "cel"] + +# Individual plugins, each pulling exactly one apl-* crate. +jwt = ["dep:apl-identity-jwt"] +oauth = ["dep:apl-delegator-oauth"] +pii = ["dep:apl-pii-scanner"] +audit = ["dep:apl-audit-logger"] +cedar = ["dep:apl-pdp-cedar-direct"] +cel = ["dep:apl-pdp-cel"] +valkey = ["dep:apl-session-valkey"] + +# Everything the facade knows how to wire, including the Valkey session +# store (redis client + rustls TLS stack). +full = ["jwt", "oauth", "pii", "audit", "cedar", "cel", "valkey"] + +[dependencies] +# Host runtime — always present, this is the point of the facade. +cpex-core = { path = "../cpex-core" } +apl-core = { path = "../apl-core" } +apl-cmf = { path = "../apl-cmf" } +apl-cpex = { path = "../apl-cpex" } + +# Bundled plugin factories — each behind its feature. +apl-identity-jwt = { path = "../apl-identity-jwt", optional = true } +apl-delegator-oauth = { path = "../apl-delegator-oauth", optional = true } +apl-pii-scanner = { path = "../apl-pii-scanner", optional = true } +apl-audit-logger = { path = "../apl-audit-logger", optional = true } +apl-pdp-cedar-direct = { path = "../apl-pdp-cedar-direct", optional = true } +apl-pdp-cel = { path = "../apl-pdp-cel", optional = true } +apl-session-valkey = { path = "../apl-session-valkey", optional = true } diff --git a/crates/cpex/src/lib.rs b/crates/cpex/src/lib.rs new file mode 100644 index 0000000..b5da2a1 --- /dev/null +++ b/crates/cpex/src/lib.rs @@ -0,0 +1,179 @@ +// Location: ./crates/cpex/src/lib.rs +// Copyright 2025 +// SPDX-License-Identifier: Apache-2.0 +// Authors: Fred Araujo + +//! CPEX host facade. +//! +//! A single dependency that re-exports the CPEX host runtime and the +//! bundled APL plugin factories, each behind a cargo feature. Hosts +//! depend on this crate instead of pinning `apl-cmf`, `apl-cpex`, +//! `apl-pdp-*`, `apl-session-*`, and friends one by one. +//! +//! # Usage +//! +//! ```toml +//! cpex = { version = "0.2.0", features = ["jwt", "oauth", "cedar", "cel", "valkey"] } +//! ``` +//! +//! ```no_run +//! use std::sync::Arc; +//! use cpex::PluginManager; +//! +//! let mgr = Arc::new(PluginManager::default()); +//! // Register every enabled plugin factory and install the APL config +//! // visitor (in-process defaults) in one call: +//! cpex::install_builtins(&mgr); +//! // ... then load a config that references the enabled `kind`s. +//! ``` +//! +//! For finer control, the building blocks are public: +//! [`register_builtin_plugins`] registers the by-kind plugin factories, +//! [`builtin_pdp_factories`] / [`builtin_session_store_factories`] return +//! the enabled factories for an [`AplOptions`] you assemble yourself, and +//! every concrete factory type is re-exported under its feature. +//! +//! # Features +//! +//! `jwt`, `oauth`, `pii`, `audit`, `cedar`, `cel` are on by default. +//! `valkey` (Valkey-backed session store; pulls a redis client and a +//! rustls TLS stack) is opt-in. `full` enables everything. + +use std::sync::Arc; + +// ----------------------------------------------------------------------------- +// Host runtime re-exports (always available) +// ----------------------------------------------------------------------------- + +// Whole-crate re-exports for advanced use (types not surfaced below). +pub use {apl_cmf, apl_core, apl_cpex, cpex_core}; + +pub use apl_core::step::PdpFactory; +pub use apl_cpex::{ + register_apl, AplOptions, DispatchCache, MemorySessionStore, SessionStore, SessionStoreFactory, +}; +pub use cpex_core::manager::PluginManager; + +// ----------------------------------------------------------------------------- +// Bundled plugin factories (feature-gated) +// ----------------------------------------------------------------------------- + +#[cfg(feature = "audit")] +pub use apl_audit_logger::{AuditLoggerFactory, KIND as AUDIT_KIND}; +#[cfg(feature = "oauth")] +pub use apl_delegator_oauth::{OAuthDelegatorFactory, KIND as OAUTH_KIND}; +#[cfg(feature = "jwt")] +pub use apl_identity_jwt::{JwtIdentityFactory, KIND as JWT_KIND}; +#[cfg(feature = "cedar")] +pub use apl_pdp_cedar_direct::CedarDirectPdpFactory; +#[cfg(feature = "cel")] +pub use apl_pdp_cel::CelPdpFactory; +#[cfg(feature = "pii")] +pub use apl_pii_scanner::{PiiScannerFactory, KIND as PII_KIND}; +#[cfg(feature = "valkey")] +pub use apl_session_valkey::{ValkeyConfig, ValkeySessionStoreFactory, KIND as VALKEY_KIND}; + +// ----------------------------------------------------------------------------- +// Registration helpers +// ----------------------------------------------------------------------------- + +/// Register every enabled by-kind plugin factory on `mgr`: identity +/// (`jwt`), delegators (`oauth`), validators (`pii`), and observers +/// (`audit`). Call before loading a config so the manager can +/// instantiate plugins whose YAML `kind:` matches. +/// +/// PDP and session-store factories are wired through [`AplOptions`] +/// instead; see [`builtin_pdp_factories`] and +/// [`builtin_session_store_factories`], or use [`install_builtins`]. +#[allow(unused_variables)] +pub fn register_builtin_plugins(mgr: &Arc) { + #[cfg(feature = "jwt")] + mgr.register_factory(JWT_KIND, Box::new(JwtIdentityFactory)); + #[cfg(feature = "oauth")] + mgr.register_factory(OAUTH_KIND, Box::new(OAuthDelegatorFactory)); + #[cfg(feature = "pii")] + mgr.register_factory(PII_KIND, Box::new(PiiScannerFactory)); + #[cfg(feature = "audit")] + mgr.register_factory(AUDIT_KIND, Box::new(AuditLoggerFactory)); +} + +/// The enabled PDP factories, ready to drop into +/// [`AplOptions::pdp_factories`]. A route's `cedar:` or `cel:` step +/// selects which one runs. +// `vec![]` can't replace the conditional pushes: each element is +// `#[cfg]`-gated on its feature, so the set is built incrementally. +#[allow(unused_mut, clippy::vec_init_then_push)] +pub fn builtin_pdp_factories() -> Vec> { + let mut factories: Vec> = Vec::new(); + #[cfg(feature = "cedar")] + factories.push(Arc::new(CedarDirectPdpFactory::new())); + #[cfg(feature = "cel")] + factories.push(Arc::new(CelPdpFactory::new())); + factories +} + +/// The enabled session-store factories, ready to drop into +/// [`AplOptions::session_store_factories`]. A `global.session_store: +/// { kind: ... }` config block selects one; absent that, the +/// [`MemorySessionStore`] default stays active. +#[allow(unused_mut, clippy::vec_init_then_push)] +pub fn builtin_session_store_factories() -> Vec> { + let mut factories: Vec> = Vec::new(); + #[cfg(feature = "valkey")] + factories.push(Arc::new(ValkeySessionStoreFactory::new())); + factories +} + +/// Register every enabled plugin factory and install the APL config +/// visitor on `mgr` with in-process defaults (a [`MemorySessionStore`] +/// and the default baseline capabilities). The enabled PDP and +/// session-store factories are wired in, so a later config load can +/// reference any of them by `kind`. +/// +/// This is the one-call path; reach for [`register_builtin_plugins`] and +/// [`AplOptions`] directly when you need to customize capabilities or the +/// default store. +pub fn install_builtins(mgr: &Arc) { + register_builtin_plugins(mgr); + + let mut opts = AplOptions::in_process(); + opts.pdp_factories = builtin_pdp_factories(); + opts.session_store_factories = builtin_session_store_factories(); + + let _visitor = register_apl(mgr, opts); +} + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn install_builtins_runs_without_panic() { + let mgr = Arc::new(PluginManager::default()); + install_builtins(&mgr); + } + + #[test] + fn pdp_factories_track_enabled_features() { + let expected = cfg!(feature = "cedar") as usize + cfg!(feature = "cel") as usize; + assert_eq!( + builtin_pdp_factories().len(), + expected, + "one PDP factory per enabled feature", + ); + } + + #[test] + fn session_store_factories_track_enabled_features() { + let expected = cfg!(feature = "valkey") as usize; + assert_eq!( + builtin_session_store_factories().len(), + expected, + "one session-store factory per enabled feature", + ); + } +}