operations: enforce community capability limits
This commit is contained in:
@@ -26,6 +26,12 @@ pub enum OperationSecurityLevel {
|
||||
Strict,
|
||||
}
|
||||
|
||||
impl Default for OperationSecurityLevel {
|
||||
fn default() -> Self {
|
||||
Self::Standard
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct EditionLimits {
|
||||
pub max_workspaces: Option<u32>,
|
||||
|
||||
@@ -5,6 +5,7 @@ use serde_json::Value;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
edition::OperationSecurityLevel,
|
||||
ids::{AuthProfileId, DescriptorId, OperationId, SampleId},
|
||||
protocol::{ExportMode, GraphqlOperationType, HttpMethod, Protocol},
|
||||
soap::{
|
||||
@@ -17,6 +18,10 @@ fn default_operation_category() -> String {
|
||||
"general".to_owned()
|
||||
}
|
||||
|
||||
fn default_operation_security_level() -> OperationSecurityLevel {
|
||||
OperationSecurityLevel::Standard
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OperationStatus {
|
||||
@@ -217,6 +222,8 @@ pub struct Operation<TSchema, TMapping> {
|
||||
#[serde(default = "default_operation_category")]
|
||||
pub category: String,
|
||||
pub protocol: Protocol,
|
||||
#[serde(default = "default_operation_security_level")]
|
||||
pub security_level: OperationSecurityLevel,
|
||||
pub status: OperationStatus,
|
||||
pub version: u32,
|
||||
pub target: Target,
|
||||
@@ -272,6 +279,7 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
auth::{AuthConfig, AuthProfile, BearerAuthConfig},
|
||||
edition::OperationSecurityLevel,
|
||||
ids::{AuthProfileId, OperationId, SampleId},
|
||||
operation::{
|
||||
ConfigExport, ExecutionConfig, GraphqlTarget, Operation, OperationStatus,
|
||||
@@ -382,6 +390,7 @@ mod tests {
|
||||
display_name: "Create Lead".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Draft,
|
||||
version: 1,
|
||||
target: Target::Rest(RestTarget {
|
||||
@@ -470,6 +479,7 @@ mod tests {
|
||||
display_name: "Create Lead".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 3,
|
||||
target: Target::Rest(RestTarget {
|
||||
@@ -514,6 +524,7 @@ mod tests {
|
||||
serde_yaml::from_str(&yaml).unwrap();
|
||||
|
||||
assert!(yaml.contains("protocol: rest"));
|
||||
assert!(yaml.contains("security_level: standard"));
|
||||
assert!(yaml.contains("export_mode: portable"));
|
||||
assert_eq!(restored, operation);
|
||||
}
|
||||
@@ -526,6 +537,7 @@ name: crm_create_lead
|
||||
display_name: Create Lead
|
||||
category: sales
|
||||
protocol: rest
|
||||
security_level: standard
|
||||
status: draft
|
||||
version: 1
|
||||
target:
|
||||
@@ -557,6 +569,7 @@ updated_at: 2026-03-25T08:10:00Z
|
||||
let restored: Operation<serde_json::Value, serde_json::Value> =
|
||||
serde_yaml::from_str(yaml).unwrap();
|
||||
|
||||
assert_eq!(restored.security_level, OperationSecurityLevel::Standard);
|
||||
assert_eq!(restored.published_at, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ pub async fn apply_postgres(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
display_name text not null,
|
||||
category text not null default 'general',
|
||||
protocol text not null,
|
||||
security_level text not null default 'standard',
|
||||
status text not null,
|
||||
current_draft_version integer not null default 1,
|
||||
latest_published_version integer null,
|
||||
@@ -217,6 +218,11 @@ pub async fn apply_postgres(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
query(
|
||||
"alter table operations add column if not exists security_level text not null default 'standard'",
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
query("update operations set workspace_id = 'ws_default' where workspace_id is null")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
@@ -2,9 +2,9 @@ use crank_core::{
|
||||
Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AsyncJobHandle, AsyncJobId,
|
||||
AuthProfile, DescriptorId, ExecutionMode, ExportMode, InvitationToken, InvocationLevel,
|
||||
InvocationLog, InvocationSource, JobStatus, MembershipRole, Operation, OperationId,
|
||||
OperationStatus, PlatformApiKey, Protocol, SampleId, Secret, SecretId, SecretVersion,
|
||||
StreamSession, StreamSessionId, StreamStatus, UsagePeriod, UsageRollup, User, UserSessionId,
|
||||
Workspace, WorkspaceId,
|
||||
OperationSecurityLevel, OperationStatus, PlatformApiKey, Protocol, SampleId, Secret, SecretId,
|
||||
SecretVersion, StreamSession, StreamSessionId, StreamStatus, UsagePeriod, UsageRollup, User,
|
||||
UserSessionId, Workspace, WorkspaceId,
|
||||
};
|
||||
use crank_mapping::MappingSet;
|
||||
use crank_schema::Schema;
|
||||
@@ -207,6 +207,7 @@ pub struct OperationSummary {
|
||||
pub display_name: String,
|
||||
pub category: String,
|
||||
pub protocol: Protocol,
|
||||
pub security_level: OperationSecurityLevel,
|
||||
pub target_url: String,
|
||||
pub target_action: String,
|
||||
pub status: OperationStatus,
|
||||
|
||||
@@ -452,6 +452,7 @@ impl PostgresRegistry {
|
||||
o.display_name,
|
||||
o.category,
|
||||
o.protocol,
|
||||
o.security_level,
|
||||
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
|
||||
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
|
||||
o.published_at as \"operation_published_at: time::OffsetDateTime\",
|
||||
@@ -523,6 +524,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.operation_created_at,
|
||||
row.operation_updated_at,
|
||||
row.operation_published_at,
|
||||
|
||||
@@ -714,6 +714,7 @@ fn build_operation_summary(
|
||||
display_name: String,
|
||||
category: String,
|
||||
protocol: String,
|
||||
security_level: String,
|
||||
target_json: Value,
|
||||
status: String,
|
||||
current_draft_version: i32,
|
||||
@@ -732,6 +733,7 @@ fn build_operation_summary(
|
||||
display_name,
|
||||
category,
|
||||
protocol: deserialize_enum_text(&protocol, "protocol")?,
|
||||
security_level: deserialize_enum_text(&security_level, "security_level")?,
|
||||
target_url,
|
||||
target_action,
|
||||
status: deserialize_enum_text(&status, "status")?,
|
||||
@@ -831,6 +833,7 @@ fn build_operation_version_record(
|
||||
display_name: String,
|
||||
category: String,
|
||||
protocol: String,
|
||||
security_level: String,
|
||||
operation_created_at: OffsetDateTime,
|
||||
operation_updated_at: OffsetDateTime,
|
||||
operation_published_at: Option<OffsetDateTime>,
|
||||
@@ -868,6 +871,7 @@ fn build_operation_version_record(
|
||||
display_name,
|
||||
category,
|
||||
protocol: deserialize_enum_text(&protocol, "protocol")?,
|
||||
security_level: deserialize_enum_text(&security_level, "security_level")?,
|
||||
status,
|
||||
version,
|
||||
target: deserialize_json_value(target_json)?,
|
||||
@@ -1130,10 +1134,10 @@ mod tests {
|
||||
AgentId, AgentOperationBinding, AgentStatus, AgentVersion, ApiKeyHeaderAuthConfig,
|
||||
AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig,
|
||||
ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, InvocationLog, JobStatus,
|
||||
MembershipRole, OperationId, OperationStatus, PlatformApiKey, PlatformApiKeyId,
|
||||
PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples,
|
||||
SecretId, StreamSession, StreamSessionId, StreamStatus, Target, ToolDescription,
|
||||
ToolExample, User, UserId, UserSessionId, Workspace, WorkspaceId,
|
||||
MembershipRole, OperationId, OperationSecurityLevel, OperationStatus, PlatformApiKey,
|
||||
PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget,
|
||||
RetryPolicy, Samples, SecretId, StreamSession, StreamSessionId, StreamStatus, Target,
|
||||
ToolDescription, ToolExample, User, UserId, UserSessionId, Workspace, WorkspaceId,
|
||||
};
|
||||
use crank_mapping::{MappingRule, MappingSet};
|
||||
use crank_schema::{Schema, SchemaKind};
|
||||
@@ -2345,6 +2349,7 @@ mod tests {
|
||||
display_name: format!("Display {id}"),
|
||||
category: "general".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status,
|
||||
version,
|
||||
target: Target::Rest(RestTarget {
|
||||
|
||||
@@ -34,6 +34,7 @@ impl PostgresRegistry {
|
||||
display_name,
|
||||
category,
|
||||
protocol,
|
||||
security_level,
|
||||
status,
|
||||
current_draft_version,
|
||||
latest_published_version,
|
||||
@@ -41,10 +42,10 @@ impl PostgresRegistry {
|
||||
updated_at,
|
||||
published_at
|
||||
) values (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9,
|
||||
$10::timestamptz,
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
|
||||
$11::timestamptz,
|
||||
$12::timestamptz
|
||||
$12::timestamptz,
|
||||
$13::timestamptz
|
||||
)",
|
||||
)
|
||||
.bind(snapshot.id.as_str())
|
||||
@@ -53,6 +54,10 @@ impl PostgresRegistry {
|
||||
.bind(&snapshot.display_name)
|
||||
.bind(&snapshot.category)
|
||||
.bind(serialize_enum_text(&snapshot.protocol, "protocol")?)
|
||||
.bind(serialize_enum_text(
|
||||
&snapshot.security_level,
|
||||
"security_level",
|
||||
)?)
|
||||
.bind(serialize_enum_text(&snapshot.status, "status")?)
|
||||
.bind(to_db_version(snapshot.version))
|
||||
.bind(
|
||||
@@ -155,12 +160,17 @@ impl PostgresRegistry {
|
||||
"update operations
|
||||
set display_name = $1,
|
||||
category = $2,
|
||||
status = $3,
|
||||
updated_at = $4::timestamptz
|
||||
where workspace_id = $5 and id = $6",
|
||||
security_level = $3,
|
||||
status = $4,
|
||||
updated_at = $5::timestamptz
|
||||
where workspace_id = $6 and id = $7",
|
||||
)
|
||||
.bind(&snapshot.display_name)
|
||||
.bind(&snapshot.category)
|
||||
.bind(serialize_enum_text(
|
||||
&snapshot.security_level,
|
||||
"security_level",
|
||||
)?)
|
||||
.bind(serialize_enum_text(&snapshot.status, "status")?)
|
||||
.bind(snapshot.updated_at)
|
||||
.bind(workspace_id.as_str())
|
||||
@@ -316,6 +326,7 @@ impl PostgresRegistry {
|
||||
operations.display_name,
|
||||
operations.category,
|
||||
operations.protocol,
|
||||
operations.security_level,
|
||||
ov.target_json,
|
||||
operations.status,
|
||||
operations.current_draft_version,
|
||||
@@ -343,6 +354,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.target_json,
|
||||
row.status,
|
||||
row.current_draft_version,
|
||||
@@ -428,6 +440,7 @@ impl PostgresRegistry {
|
||||
operations.display_name,
|
||||
operations.category,
|
||||
operations.protocol,
|
||||
operations.security_level,
|
||||
ov.target_json,
|
||||
operations.status,
|
||||
operations.current_draft_version,
|
||||
@@ -454,6 +467,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.target_json,
|
||||
row.status,
|
||||
row.current_draft_version,
|
||||
@@ -480,6 +494,7 @@ impl PostgresRegistry {
|
||||
o.display_name,
|
||||
o.category,
|
||||
o.protocol,
|
||||
o.security_level,
|
||||
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
|
||||
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
|
||||
o.published_at as \"operation_published_at: time::OffsetDateTime\",
|
||||
@@ -516,6 +531,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.operation_created_at,
|
||||
row.operation_updated_at,
|
||||
row.operation_published_at,
|
||||
@@ -552,6 +568,7 @@ impl PostgresRegistry {
|
||||
o.display_name,
|
||||
o.category,
|
||||
o.protocol,
|
||||
o.security_level,
|
||||
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
|
||||
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
|
||||
o.published_at as \"operation_published_at: time::OffsetDateTime\",
|
||||
@@ -589,6 +606,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.operation_created_at,
|
||||
row.operation_updated_at,
|
||||
row.operation_published_at,
|
||||
@@ -691,6 +709,7 @@ impl PostgresRegistry {
|
||||
o.display_name,
|
||||
o.category,
|
||||
o.protocol,
|
||||
o.security_level,
|
||||
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
|
||||
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
|
||||
o.published_at as \"operation_published_at: time::OffsetDateTime\",
|
||||
@@ -727,6 +746,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.operation_created_at,
|
||||
row.operation_updated_at,
|
||||
row.operation_published_at,
|
||||
@@ -760,6 +780,7 @@ impl PostgresRegistry {
|
||||
o.display_name,
|
||||
o.category,
|
||||
o.protocol,
|
||||
o.security_level,
|
||||
o.created_at as \"operation_created_at!: time::OffsetDateTime\",
|
||||
o.updated_at as \"operation_updated_at!: time::OffsetDateTime\",
|
||||
o.published_at as \"operation_published_at: time::OffsetDateTime\",
|
||||
@@ -796,6 +817,7 @@ impl PostgresRegistry {
|
||||
row.display_name,
|
||||
row.category,
|
||||
row.protocol,
|
||||
row.security_level,
|
||||
row.operation_created_at,
|
||||
row.operation_updated_at,
|
||||
row.operation_published_at,
|
||||
|
||||
@@ -718,9 +718,9 @@ mod tests {
|
||||
use crank_core::{
|
||||
AggregationMode, DescriptorId, ExecutionConfig, ExecutionMode, GeneratedDraft,
|
||||
GeneratedDraftStatus, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod,
|
||||
Operation, OperationId, OperationStatus, Protocol, ProtocolOptions, RestTarget, Samples,
|
||||
SoapBindingStyle, SoapOperationMetadata, SoapTarget, SoapVersion, StreamingConfig, Target,
|
||||
ToolDescription, ToolExample, ToolFamilyConfig, TransportBehavior,
|
||||
Operation, OperationId, OperationSecurityLevel, OperationStatus, Protocol, ProtocolOptions,
|
||||
RestTarget, Samples, SoapBindingStyle, SoapOperationMetadata, SoapTarget, SoapVersion,
|
||||
StreamingConfig, Target, ToolDescription, ToolExample, ToolFamilyConfig, TransportBehavior,
|
||||
WebsocketProtocolOptions, WebsocketTarget,
|
||||
};
|
||||
use crank_mapping::{MappingRule, MappingSet};
|
||||
@@ -1654,6 +1654,7 @@ mod tests {
|
||||
display_name: "Create Lead".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Rest(RestTarget {
|
||||
@@ -1717,6 +1718,7 @@ mod tests {
|
||||
display_name: "Create Lead GraphQL".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Graphql,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Graphql(GraphqlTarget {
|
||||
@@ -1793,6 +1795,7 @@ mod tests {
|
||||
display_name: "Unary Echo gRPC".to_owned(),
|
||||
category: "support".to_owned(),
|
||||
protocol: Protocol::Grpc,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Grpc(GrpcTarget {
|
||||
@@ -1872,6 +1875,7 @@ mod tests {
|
||||
display_name: "Server Stream Echo gRPC".to_owned(),
|
||||
category: "support".to_owned(),
|
||||
protocol: Protocol::Grpc,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Grpc(GrpcTarget {
|
||||
@@ -1950,6 +1954,7 @@ mod tests {
|
||||
display_name: "Create Lead SOAP".to_owned(),
|
||||
category: "sales".to_owned(),
|
||||
protocol: Protocol::Soap,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Soap(SoapTarget {
|
||||
@@ -2039,6 +2044,7 @@ mod tests {
|
||||
display_name: "Billing Log Window".to_owned(),
|
||||
category: "ops".to_owned(),
|
||||
protocol: Protocol::Rest,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Rest(RestTarget {
|
||||
@@ -2139,6 +2145,7 @@ mod tests {
|
||||
display_name: "Telemetry Window WebSocket".to_owned(),
|
||||
category: "ops".to_owned(),
|
||||
protocol: Protocol::Websocket,
|
||||
security_level: OperationSecurityLevel::Standard,
|
||||
status: OperationStatus::Published,
|
||||
version: 1,
|
||||
target: Target::Websocket(WebsocketTarget {
|
||||
|
||||
Reference in New Issue
Block a user