Remove enterprise leftovers from community
This commit is contained in:
@@ -989,24 +989,18 @@ fn credential_allows_security_level(
|
||||
fn security_level_rank(level: OperationSecurityLevel) -> u8 {
|
||||
match level {
|
||||
OperationSecurityLevel::Standard => 0,
|
||||
OperationSecurityLevel::Elevated => 1,
|
||||
OperationSecurityLevel::Strict => 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_security_level(level: OperationSecurityLevel) -> &'static str {
|
||||
match level {
|
||||
OperationSecurityLevel::Standard => "standard",
|
||||
OperationSecurityLevel::Elevated => "elevated",
|
||||
OperationSecurityLevel::Strict => "strict",
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_machine_access_mode(mode: crank_core::MachineAccessMode) -> &'static str {
|
||||
match mode {
|
||||
crank_core::MachineAccessMode::StaticAgentKey => "static_agent_key",
|
||||
crank_core::MachineAccessMode::ShortLivedToken => "short_lived_token",
|
||||
crank_core::MachineAccessMode::OneTimeToken => "one_time_token",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
edition::{MachineAccessMode, OperationSecurityLevel},
|
||||
ids::{AgentId, AuthProfileId, OperationId, SecretId, WorkspaceId},
|
||||
ids::{AuthProfileId, SecretId, WorkspaceId},
|
||||
protocol::AuthKind,
|
||||
};
|
||||
|
||||
@@ -64,55 +63,13 @@ pub struct AuthProfile {
|
||||
pub updated_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AgentTokenGrantType {
|
||||
AgentKey,
|
||||
RefreshToken,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IssueAgentTokenRequest {
|
||||
pub grant_type: AgentTokenGrantType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub agent_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_token: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub scope: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IssueOneTimeAgentTokenRequest {
|
||||
pub agent_key: String,
|
||||
pub operation_id: OperationId,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub scope: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IssuedAgentTokenResponse {
|
||||
pub access_token: String,
|
||||
pub token_type: String,
|
||||
pub expires_in: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_token: Option<String>,
|
||||
pub machine_access_mode: MachineAccessMode,
|
||||
pub security_level: OperationSecurityLevel,
|
||||
pub agent_id: AgentId,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
|
||||
use super::{
|
||||
AgentTokenGrantType, ApiKeyHeaderAuthConfig, AuthConfig, AuthProfile,
|
||||
IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, IssuedAgentTokenResponse,
|
||||
};
|
||||
use super::{ApiKeyHeaderAuthConfig, AuthConfig, AuthProfile};
|
||||
use crate::{
|
||||
edition::{MachineAccessMode, OperationSecurityLevel},
|
||||
ids::{AuthProfileId, SecretId, WorkspaceId},
|
||||
protocol::AuthKind,
|
||||
};
|
||||
@@ -137,59 +94,4 @@ mod tests {
|
||||
assert_eq!(value["created_at"], json!("2026-03-25T12:00:00Z"));
|
||||
assert_eq!(value["updated_at"], json!("2026-03-25T12:05:00Z"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_agent_token_request_serializes_stable_contract() {
|
||||
let request = IssueAgentTokenRequest {
|
||||
grant_type: AgentTokenGrantType::AgentKey,
|
||||
agent_key: Some("crk_agent_secret".to_owned()),
|
||||
refresh_token: None,
|
||||
scope: vec!["tools:call".to_owned()],
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&request).unwrap();
|
||||
|
||||
assert_eq!(value["grant_type"], json!("agent_key"));
|
||||
assert_eq!(value["agent_key"], json!("crk_agent_secret"));
|
||||
assert_eq!(value["scope"], json!(["tools:call"]));
|
||||
assert_eq!(value.get("refresh_token"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_one_time_agent_token_request_serializes_stable_contract() {
|
||||
let request = IssueOneTimeAgentTokenRequest {
|
||||
agent_key: "crk_agent_secret".to_owned(),
|
||||
operation_id: "op_01".into(),
|
||||
scope: vec!["tools:call".to_owned()],
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&request).unwrap();
|
||||
|
||||
assert_eq!(value["agent_key"], json!("crk_agent_secret"));
|
||||
assert_eq!(value["operation_id"], json!("op_01"));
|
||||
assert_eq!(value["scope"], json!(["tools:call"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issued_agent_token_response_serializes_machine_access_metadata() {
|
||||
let response = IssuedAgentTokenResponse {
|
||||
access_token: "tok_01".to_owned(),
|
||||
token_type: "Bearer".to_owned(),
|
||||
expires_in: 300,
|
||||
refresh_token: Some("rfr_01".to_owned()),
|
||||
machine_access_mode: MachineAccessMode::ShortLivedToken,
|
||||
security_level: OperationSecurityLevel::Elevated,
|
||||
agent_id: "agt_01".into(),
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(&response).unwrap();
|
||||
|
||||
assert_eq!(value["access_token"], json!("tok_01"));
|
||||
assert_eq!(value["token_type"], json!("Bearer"));
|
||||
assert_eq!(value["expires_in"], json!(300));
|
||||
assert_eq!(value["refresh_token"], json!("rfr_01"));
|
||||
assert_eq!(value["machine_access_mode"], json!("short_lived_token"));
|
||||
assert_eq!(value["security_level"], json!("elevated"));
|
||||
assert_eq!(value["agent_id"], json!("agt_01"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,18 @@ use crate::Protocol;
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProductEdition {
|
||||
Community,
|
||||
Enterprise,
|
||||
Cloud,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MachineAccessMode {
|
||||
StaticAgentKey,
|
||||
ShortLivedToken,
|
||||
OneTimeToken,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OperationSecurityLevel {
|
||||
Standard,
|
||||
Elevated,
|
||||
Strict,
|
||||
}
|
||||
|
||||
impl Default for OperationSecurityLevel {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, IssuedAgentTokenResponse,
|
||||
MachineAccessMode, Membership, MembershipRole, OperationSecurityLevel, PlatformApiKeyScope,
|
||||
User, UserId, WorkspaceId,
|
||||
MachineAccessMode, Membership, OperationSecurityLevel, PlatformApiKeyScope, User, WorkspaceId,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VerifiedMachineCredential {
|
||||
@@ -32,70 +28,6 @@ pub trait MachineCredentialVerifier: Send + Sync {
|
||||
|
||||
pub type SharedMachineCredentialVerifier = Arc<dyn MachineCredentialVerifier>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TokenIssuerActor {
|
||||
pub user_id: UserId,
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub role: MembershipRole,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum TokenIssuerError {
|
||||
#[error("machine tokens are not available in this edition")]
|
||||
NotSupportedInEdition,
|
||||
#[error("invalid grant: {0}")]
|
||||
InvalidGrant(String),
|
||||
#[error("agent key is unknown")]
|
||||
AgentKeyUnknown,
|
||||
#[error("operation is not strict")]
|
||||
OperationNotStrict,
|
||||
#[error("operation is not published for agent")]
|
||||
OperationNotPublishedForAgent,
|
||||
#[error("registry failure: {0}")]
|
||||
RegistryFailure(String),
|
||||
#[error("replay guard failure: {0}")]
|
||||
ReplayGuardFailure(String),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait MachineTokenIssuer: Send + Sync {
|
||||
async fn issue_short_lived(
|
||||
&self,
|
||||
request: IssueAgentTokenRequest,
|
||||
actor: &TokenIssuerActor,
|
||||
) -> Result<IssuedAgentTokenResponse, TokenIssuerError>;
|
||||
|
||||
async fn issue_one_time(
|
||||
&self,
|
||||
request: IssueOneTimeAgentTokenRequest,
|
||||
actor: &TokenIssuerActor,
|
||||
) -> Result<IssuedAgentTokenResponse, TokenIssuerError>;
|
||||
}
|
||||
|
||||
pub type SharedMachineTokenIssuer = Arc<dyn MachineTokenIssuer>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoMachineTokenIssuer;
|
||||
|
||||
#[async_trait]
|
||||
impl MachineTokenIssuer for NoMachineTokenIssuer {
|
||||
async fn issue_short_lived(
|
||||
&self,
|
||||
_request: IssueAgentTokenRequest,
|
||||
_actor: &TokenIssuerActor,
|
||||
) -> Result<IssuedAgentTokenResponse, TokenIssuerError> {
|
||||
Err(TokenIssuerError::NotSupportedInEdition)
|
||||
}
|
||||
|
||||
async fn issue_one_time(
|
||||
&self,
|
||||
_request: IssueOneTimeAgentTokenRequest,
|
||||
_actor: &TokenIssuerActor,
|
||||
) -> Result<IssuedAgentTokenResponse, TokenIssuerError> {
|
||||
Err(TokenIssuerError::NotSupportedInEdition)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum IdentityProviderKind {
|
||||
Password,
|
||||
@@ -126,64 +58,8 @@ pub struct LoginPayload {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SsoAuthorizeRequest {
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub provider_id: String,
|
||||
pub base_origin: String,
|
||||
pub return_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SsoAuthorizeRedirect {
|
||||
pub authorization_url: String,
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SsoCallbackRequest {
|
||||
pub workspace_id: WorkspaceId,
|
||||
pub provider_id: String,
|
||||
pub code: String,
|
||||
pub base_origin: String,
|
||||
pub return_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TwoFactorPendingIdentity {
|
||||
pub user_id: UserId,
|
||||
pub workspace_id: Option<WorkspaceId>,
|
||||
pub return_to: Option<String>,
|
||||
pub expires_at: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TwoFactorStatus {
|
||||
pub enabled: bool,
|
||||
pub pending_setup: bool,
|
||||
pub recovery_codes_remaining: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TwoFactorSetup {
|
||||
pub secret: String,
|
||||
pub otpauth_url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TwoFactorActivation {
|
||||
pub recovery_codes: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct TwoFactorChallenge {
|
||||
pub code: Option<String>,
|
||||
pub recovery_code: Option<String>,
|
||||
}
|
||||
|
||||
pub enum LoginOutcome {
|
||||
Authenticated(AuthenticatedIdentity),
|
||||
TwoFactorRequired(TwoFactorPendingIdentity),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -193,109 +69,6 @@ pub trait IdentityProvider: Send + Sync {
|
||||
fn kind(&self) -> IdentityProviderKind;
|
||||
|
||||
async fn login_password(&self, payload: LoginPayload) -> Result<LoginOutcome, IdentityError>;
|
||||
|
||||
async fn begin_sso(
|
||||
&self,
|
||||
_request: SsoAuthorizeRequest,
|
||||
) -> Result<SsoAuthorizeRedirect, IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
|
||||
async fn complete_sso(
|
||||
&self,
|
||||
_request: SsoCallbackRequest,
|
||||
) -> Result<LoginOutcome, IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
|
||||
async fn get_two_factor_status(
|
||||
&self,
|
||||
_user_id: &UserId,
|
||||
) -> Result<TwoFactorStatus, IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
|
||||
async fn begin_two_factor_setup(
|
||||
&self,
|
||||
_user_id: &UserId,
|
||||
_email: &str,
|
||||
) -> Result<TwoFactorSetup, IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
|
||||
async fn activate_two_factor(
|
||||
&self,
|
||||
_user_id: &UserId,
|
||||
_code: &str,
|
||||
) -> Result<TwoFactorActivation, IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
|
||||
async fn disable_two_factor(
|
||||
&self,
|
||||
_user_id: &UserId,
|
||||
_challenge: TwoFactorChallenge,
|
||||
) -> Result<(), IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
|
||||
async fn verify_two_factor_login(
|
||||
&self,
|
||||
_pending: &TwoFactorPendingIdentity,
|
||||
_challenge: TwoFactorChallenge,
|
||||
) -> Result<AuthenticatedIdentity, IdentityError> {
|
||||
Err(IdentityError::NotSupportedForProvider)
|
||||
}
|
||||
}
|
||||
|
||||
pub type SharedIdentityProvider = Arc<dyn IdentityProvider>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{MachineTokenIssuer, NoMachineTokenIssuer, TokenIssuerActor, TokenIssuerError};
|
||||
use crate::{
|
||||
AgentTokenGrantType, IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, MembershipRole,
|
||||
UserId, WorkspaceId,
|
||||
};
|
||||
|
||||
fn actor() -> TokenIssuerActor {
|
||||
TokenIssuerActor {
|
||||
user_id: UserId::new("user_01"),
|
||||
workspace_id: WorkspaceId::new("ws_01"),
|
||||
role: MembershipRole::Owner,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_machine_token_issuer_rejects_short_lived_issue() {
|
||||
let result = NoMachineTokenIssuer
|
||||
.issue_short_lived(
|
||||
IssueAgentTokenRequest {
|
||||
grant_type: AgentTokenGrantType::AgentKey,
|
||||
agent_key: Some("crk_agent_secret".to_owned()),
|
||||
refresh_token: None,
|
||||
scope: vec![],
|
||||
},
|
||||
&actor(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(result, Err(TokenIssuerError::NotSupportedInEdition));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_machine_token_issuer_rejects_one_time_issue() {
|
||||
let result = NoMachineTokenIssuer
|
||||
.issue_one_time(
|
||||
IssueOneTimeAgentTokenRequest {
|
||||
agent_key: "crk_agent_secret".to_owned(),
|
||||
operation_id: "op_01".into(),
|
||||
scope: vec![],
|
||||
},
|
||||
&actor(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(result, Err(TokenIssuerError::NotSupportedInEdition));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{TenantId, UsageRollup};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum BillingGate {
|
||||
Allow,
|
||||
Deny { reason: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum BillingError {
|
||||
#[error("billing integration is not available in this edition")]
|
||||
NotSupportedInEdition,
|
||||
#[error("billing provider failure: {0}")]
|
||||
Provider(String),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait BillingHook: Send + Sync {
|
||||
async fn on_rollup(&self, rollup: UsageRollup) -> Result<(), BillingError>;
|
||||
|
||||
async fn check_billing_gate(&self, tenant: &TenantId) -> Result<BillingGate, BillingError>;
|
||||
}
|
||||
|
||||
pub type SharedBillingHook = Arc<dyn BillingHook>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoopBillingHook;
|
||||
|
||||
#[async_trait]
|
||||
impl BillingHook for NoopBillingHook {
|
||||
async fn on_rollup(&self, _rollup: UsageRollup) -> Result<(), BillingError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_billing_gate(&self, _tenant: &TenantId) -> Result<BillingGate, BillingError> {
|
||||
Ok(BillingGate::Allow)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BillingGate, BillingHook, NoopBillingHook};
|
||||
use crate::{TenantId, UsagePeriod, UsageRollup, WorkspaceId};
|
||||
|
||||
#[tokio::test]
|
||||
async fn noop_billing_hook_allows_default_gate_and_rollups() {
|
||||
let hook = NoopBillingHook;
|
||||
|
||||
hook.on_rollup(UsageRollup {
|
||||
workspace_id: WorkspaceId::new("ws_01"),
|
||||
agent_id: None,
|
||||
operation_id: None,
|
||||
period: UsagePeriod::Last24Hours,
|
||||
calls_total: 10,
|
||||
calls_ok: 9,
|
||||
calls_error: 1,
|
||||
p50_ms: 12,
|
||||
p95_ms: 42,
|
||||
p99_ms: 77,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let gate = hook
|
||||
.check_billing_gate(&TenantId::new("tenant_01"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(gate, BillingGate::Allow);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
pub mod access;
|
||||
pub mod audit;
|
||||
pub mod auth;
|
||||
pub mod billing;
|
||||
pub mod capability;
|
||||
pub mod metering;
|
||||
pub mod protocol;
|
||||
pub mod tenancy;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{TenantId, WorkspaceId};
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct TenantResolutionContext {
|
||||
pub workspace_id: Option<WorkspaceId>,
|
||||
pub workspace_slug: Option<String>,
|
||||
pub host: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum TenancyError {
|
||||
#[error("tenant lookup is not supported in this edition")]
|
||||
UnsupportedLookup,
|
||||
#[error("internal tenancy failure: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait TenantController: Send + Sync {
|
||||
fn resolve_tenant(&self, request: &TenantResolutionContext) -> Result<TenantId, TenancyError>;
|
||||
|
||||
async fn list_workspaces_for_tenant(
|
||||
&self,
|
||||
tenant: &TenantId,
|
||||
) -> Result<Vec<WorkspaceId>, TenancyError>;
|
||||
}
|
||||
|
||||
pub type SharedTenantController = Arc<dyn TenantController>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SingleTenantController {
|
||||
tenant_id: TenantId,
|
||||
}
|
||||
|
||||
impl Default for SingleTenantController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tenant_id: TenantId::new("default"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TenantController for SingleTenantController {
|
||||
fn resolve_tenant(&self, _request: &TenantResolutionContext) -> Result<TenantId, TenancyError> {
|
||||
Ok(self.tenant_id.clone())
|
||||
}
|
||||
|
||||
async fn list_workspaces_for_tenant(
|
||||
&self,
|
||||
_tenant: &TenantId,
|
||||
) -> Result<Vec<WorkspaceId>, TenancyError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{SingleTenantController, TenantController, TenantResolutionContext};
|
||||
use crate::TenantId;
|
||||
|
||||
#[tokio::test]
|
||||
async fn single_tenant_controller_returns_default_tenant() {
|
||||
let controller = SingleTenantController::default();
|
||||
|
||||
let tenant = controller
|
||||
.resolve_tenant(&TenantResolutionContext::default())
|
||||
.unwrap();
|
||||
let workspaces = controller
|
||||
.list_workspaces_for_tenant(&tenant)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tenant, TenantId::new("default"));
|
||||
assert!(workspaces.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,6 @@ define_id!(ToolId);
|
||||
define_id!(SampleId);
|
||||
define_id!(AuthProfileId);
|
||||
define_id!(WorkspaceId);
|
||||
define_id!(TenantId);
|
||||
define_id!(UserId);
|
||||
define_id!(UserSessionId);
|
||||
define_id!(AgentId);
|
||||
|
||||
@@ -17,9 +17,8 @@ pub use access::{
|
||||
};
|
||||
pub use agent::{Agent, AgentOperationBinding, AgentStatus, AgentVersion};
|
||||
pub use auth::{
|
||||
AgentTokenGrantType, ApiKeyHeaderAuthConfig, ApiKeyQueryAuthConfig, AuthConfig, AuthProfile,
|
||||
BasicAuthConfig, BearerAuthConfig, IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest,
|
||||
IssuedAgentTokenResponse,
|
||||
ApiKeyHeaderAuthConfig, ApiKeyQueryAuthConfig, AuthConfig, AuthProfile, BasicAuthConfig,
|
||||
BearerAuthConfig,
|
||||
};
|
||||
pub use cache::{
|
||||
CacheBackend, CacheScope, CacheStoreError, CachedHeader, CachedResponse,
|
||||
@@ -38,14 +37,8 @@ pub use ext::audit::{
|
||||
};
|
||||
pub use ext::auth::{
|
||||
AuthenticatedIdentity, IdentityError, IdentityProvider, IdentityProviderKind, LoginOutcome,
|
||||
LoginPayload, MachineCredentialVerifier, MachineCredentialVerifierError, MachineTokenIssuer,
|
||||
NoMachineTokenIssuer, SharedIdentityProvider, SharedMachineCredentialVerifier,
|
||||
SharedMachineTokenIssuer, SsoAuthorizeRedirect, SsoAuthorizeRequest, SsoCallbackRequest,
|
||||
TokenIssuerActor, TokenIssuerError, TwoFactorActivation, TwoFactorChallenge,
|
||||
TwoFactorPendingIdentity, TwoFactorSetup, TwoFactorStatus, VerifiedMachineCredential,
|
||||
};
|
||||
pub use ext::billing::{
|
||||
BillingError, BillingGate, BillingHook, NoopBillingHook, SharedBillingHook,
|
||||
LoginPayload, MachineCredentialVerifier, MachineCredentialVerifierError,
|
||||
SharedIdentityProvider, SharedMachineCredentialVerifier, VerifiedMachineCredential,
|
||||
};
|
||||
pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile};
|
||||
pub use ext::metering::{MeteringEvent, MeteringSink, NoopMeteringSink, SharedMeteringSink};
|
||||
@@ -54,13 +47,9 @@ pub use ext::protocol::{
|
||||
ProtocolAdapter, ProtocolAdapterError, ResponseCacheScope, RuntimeRequestContext,
|
||||
SharedProtocolAdapter,
|
||||
};
|
||||
pub use ext::tenancy::{
|
||||
SharedTenantController, SingleTenantController, TenancyError, TenantController,
|
||||
TenantResolutionContext,
|
||||
};
|
||||
pub use ids::{
|
||||
AgentId, AuditEventId, AuthProfileId, DescriptorId, InvitationId, InvocationLogId, OperationId,
|
||||
PlatformApiKeyId, SampleId, SecretId, TenantId, ToolId, UserId, UserSessionId, WorkspaceId,
|
||||
PlatformApiKeyId, SampleId, SecretId, ToolId, UserId, UserSessionId, WorkspaceId,
|
||||
};
|
||||
pub use observability::{
|
||||
InvocationLevel, InvocationLog, InvocationSource, InvocationStatus, UsagePeriod, UsageRollup,
|
||||
|
||||
@@ -56,8 +56,6 @@ fn empty_target_context(context: MappingTargetContext) -> Value {
|
||||
("query".to_owned(), Value::Object(Map::new())),
|
||||
("headers".to_owned(), Value::Object(Map::new())),
|
||||
("body".to_owned(), Value::Object(Map::new())),
|
||||
("variables".to_owned(), Value::Object(Map::new())),
|
||||
("grpc".to_owned(), Value::Object(Map::new())),
|
||||
])),
|
||||
)])),
|
||||
MappingTargetContext::Output => Value::Object(Map::from_iter([(
|
||||
|
||||
@@ -9,11 +9,8 @@ pub enum JsonPathRoot {
|
||||
RequestQuery,
|
||||
RequestHeaders,
|
||||
RequestBody,
|
||||
RequestVariables,
|
||||
RequestGrpc,
|
||||
ResponseBody,
|
||||
ResponseData,
|
||||
ResponseGrpc,
|
||||
Output,
|
||||
}
|
||||
|
||||
@@ -25,11 +22,8 @@ impl JsonPathRoot {
|
||||
Self::RequestQuery => "request.query",
|
||||
Self::RequestHeaders => "request.headers",
|
||||
Self::RequestBody => "request.body",
|
||||
Self::RequestVariables => "request.variables",
|
||||
Self::RequestGrpc => "request.grpc",
|
||||
Self::ResponseBody => "response.body",
|
||||
Self::ResponseData => "response.data",
|
||||
Self::ResponseGrpc => "response.grpc",
|
||||
Self::Output => "output",
|
||||
}
|
||||
}
|
||||
@@ -41,11 +35,8 @@ impl JsonPathRoot {
|
||||
Self::RequestQuery => &["request", "query"],
|
||||
Self::RequestHeaders => &["request", "headers"],
|
||||
Self::RequestBody => &["request", "body"],
|
||||
Self::RequestVariables => &["request", "variables"],
|
||||
Self::RequestGrpc => &["request", "grpc"],
|
||||
Self::ResponseBody => &["response", "body"],
|
||||
Self::ResponseData => &["response", "data"],
|
||||
Self::ResponseGrpc => &["response", "grpc"],
|
||||
Self::Output => &["output"],
|
||||
}
|
||||
}
|
||||
@@ -125,16 +116,13 @@ impl JsonPath {
|
||||
}
|
||||
|
||||
fn match_root(input: &str) -> Option<(JsonPathRoot, &str)> {
|
||||
const ROOTS: [(&str, JsonPathRoot); 11] = [
|
||||
("request.variables", JsonPathRoot::RequestVariables),
|
||||
const ROOTS: [(&str, JsonPathRoot); 8] = [
|
||||
("request.headers", JsonPathRoot::RequestHeaders),
|
||||
("response.body", JsonPathRoot::ResponseBody),
|
||||
("response.data", JsonPathRoot::ResponseData),
|
||||
("response.grpc", JsonPathRoot::ResponseGrpc),
|
||||
("request.path", JsonPathRoot::RequestPath),
|
||||
("request.query", JsonPathRoot::RequestQuery),
|
||||
("request.body", JsonPathRoot::RequestBody),
|
||||
("request.grpc", JsonPathRoot::RequestGrpc),
|
||||
("output", JsonPathRoot::Output),
|
||||
("mcp", JsonPathRoot::Mcp),
|
||||
];
|
||||
|
||||
@@ -118,9 +118,7 @@ fn target_root_context(root: JsonPathRoot) -> Option<MappingTargetContext> {
|
||||
JsonPathRoot::RequestPath
|
||||
| JsonPathRoot::RequestQuery
|
||||
| JsonPathRoot::RequestHeaders
|
||||
| JsonPathRoot::RequestBody
|
||||
| JsonPathRoot::RequestVariables
|
||||
| JsonPathRoot::RequestGrpc => Some(MappingTargetContext::Input),
|
||||
| JsonPathRoot::RequestBody => Some(MappingTargetContext::Input),
|
||||
JsonPathRoot::Output => Some(MappingTargetContext::Output),
|
||||
_ => None,
|
||||
}
|
||||
@@ -136,9 +134,7 @@ fn validate_source_root(
|
||||
MappingTargetContext::Output => {
|
||||
matches!(
|
||||
root,
|
||||
JsonPathRoot::ResponseBody
|
||||
| JsonPathRoot::ResponseData
|
||||
| JsonPathRoot::ResponseGrpc
|
||||
JsonPathRoot::ResponseBody | JsonPathRoot::ResponseData
|
||||
)
|
||||
}
|
||||
MappingTargetContext::Mixed => false,
|
||||
@@ -166,8 +162,6 @@ fn validate_target_root(
|
||||
| JsonPathRoot::RequestQuery
|
||||
| JsonPathRoot::RequestHeaders
|
||||
| JsonPathRoot::RequestBody
|
||||
| JsonPathRoot::RequestVariables
|
||||
| JsonPathRoot::RequestGrpc
|
||||
),
|
||||
MappingTargetContext::Output => root == JsonPathRoot::Output,
|
||||
MappingTargetContext::Mixed => false,
|
||||
|
||||
@@ -25,22 +25,6 @@ pub enum RegistryError {
|
||||
PlatformApiKeyNotFound { key_id: String },
|
||||
#[error("secret {secret_id} was not found")]
|
||||
SecretNotFound { secret_id: String },
|
||||
#[error("stream session {session_id} was not found")]
|
||||
StreamSessionNotFound { session_id: String },
|
||||
#[error("async job {job_id} was not found")]
|
||||
AsyncJobNotFound { job_id: String },
|
||||
#[error("invalid stream session transition for {session_id}: {from} -> {to}")]
|
||||
InvalidStreamSessionTransition {
|
||||
session_id: String,
|
||||
from: String,
|
||||
to: String,
|
||||
},
|
||||
#[error("invalid async job transition for {job_id}: {from} -> {to}")]
|
||||
InvalidAsyncJobTransition {
|
||||
job_id: String,
|
||||
from: String,
|
||||
to: String,
|
||||
},
|
||||
#[error("secret with name {name} already exists in workspace {workspace_id}")]
|
||||
SecretNameAlreadyExists { workspace_id: String, name: String },
|
||||
#[error("secret {secret_id} is referenced by auth profile {auth_profile_id}")]
|
||||
|
||||
@@ -584,7 +584,7 @@ fn join_target_url(base: &str, path: &str) -> String {
|
||||
return base.to_owned();
|
||||
}
|
||||
|
||||
if path.starts_with("http://") || path.starts_with("https://") || path.starts_with("grpc://") {
|
||||
if path.starts_with("http://") || path.starts_with("https://") {
|
||||
return path.to_owned();
|
||||
}
|
||||
|
||||
|
||||
@@ -706,18 +706,18 @@ mod tests {
|
||||
};
|
||||
|
||||
store
|
||||
.put_bucket("tenant:alpha", bucket, Duration::from_secs(30))
|
||||
.put_bucket("workspace:alpha", bucket, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store.get_bucket("tenant:alpha").await.unwrap(),
|
||||
store.get_bucket("workspace:alpha").await.unwrap(),
|
||||
Some(bucket)
|
||||
);
|
||||
|
||||
store.delete_bucket("tenant:alpha").await.unwrap();
|
||||
store.delete_bucket("workspace:alpha").await.unwrap();
|
||||
|
||||
assert_eq!(store.get_bucket("tenant:alpha").await.unwrap(), None);
|
||||
assert_eq!(store.get_bucket("workspace:alpha").await.unwrap(), None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user