Public release release-e47f9c27bbeb
Source commit: e47f9c27bbebe6759f6b6fe7b12fd00bb20d116d Public tree identity: sha256:4c1af1dfd67d3f2b5088531ea8727b11124b013d2251a4d5088f51a6424c0196
This commit is contained in:
parent
3a4d4fa7ae
commit
9223c54939
30 changed files with 4195 additions and 434 deletions
|
|
@ -364,7 +364,13 @@ pub fn agent_workflow_request_scope_from_payload(
|
|||
.get("task_instance")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| "launch_task agent scope is missing task_instance".to_owned())?;
|
||||
(process, Some(TaskInstanceId::from(task)))
|
||||
(
|
||||
process,
|
||||
Some(
|
||||
TaskInstanceId::try_new(task)
|
||||
.map_err(|error| format!("malformed launch_task task instance: {error}"))?,
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
|
|
@ -373,10 +379,13 @@ pub fn agent_workflow_request_scope_from_payload(
|
|||
}
|
||||
};
|
||||
AgentWorkflowRequestScope::new(
|
||||
TenantId::from(tenant),
|
||||
ProjectId::from(project),
|
||||
TenantId::try_new(tenant)
|
||||
.map_err(|error| format!("malformed agent workflow tenant: {error}"))?,
|
||||
ProjectId::try_new(project)
|
||||
.map_err(|error| format!("malformed agent workflow project: {error}"))?,
|
||||
request_kind,
|
||||
ProcessId::from(process),
|
||||
ProcessId::try_new(process)
|
||||
.map_err(|error| format!("malformed agent workflow process: {error}"))?,
|
||||
task,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,35 @@
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use serde::{de::Error as _, Deserializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const MAX_EXTERNAL_ID_BYTES: usize = 255;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OpaqueTokenError {
|
||||
reason: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OpaqueTokenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.reason)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for OpaqueTokenError {}
|
||||
|
||||
pub fn validate_opaque_token(value: &str, max_bytes: usize) -> Result<(), OpaqueTokenError> {
|
||||
let reason = if value.trim().is_empty() {
|
||||
Some("value must not be empty or whitespace-only".to_owned())
|
||||
} else if value.len() > max_bytes {
|
||||
Some(format!("value exceeds the {max_bytes}-byte limit"))
|
||||
} else if value.chars().any(char::is_control) {
|
||||
Some("control characters are forbidden".to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
reason.map_or(Ok(()), |reason| Err(OpaqueTokenError { reason }))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct IdParseError {
|
||||
id_type: &'static str,
|
||||
|
|
@ -55,7 +83,8 @@ fn validate_id(value: &str, id_type: &'static str) -> Result<(), IdParseError> {
|
|||
|
||||
macro_rules! id_type {
|
||||
($name:ident) => {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[cfg_attr(target_arch = "wasm32", derive(Deserialize))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct $name(String);
|
||||
|
||||
impl $name {
|
||||
|
|
@ -81,6 +110,17 @@ macro_rules! id_type {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl<'de> Deserialize<'de> for $name {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Self::try_new(value).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
|
|
@ -134,4 +174,38 @@ mod tests {
|
|||
assert_hostile_values(TenantId::try_new);
|
||||
assert_hostile_values(UserId::try_new);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_identifier_type_validates_during_deserialization() {
|
||||
macro_rules! assert_deserialization {
|
||||
($id:ty) => {
|
||||
assert!(serde_json::from_str::<$id>(r#""valid-id""#).is_ok());
|
||||
let error = serde_json::from_str::<$id>(r#""hostile id!""#).unwrap_err();
|
||||
assert!(
|
||||
error.to_string().contains("is invalid"),
|
||||
"{} produced an unexpected error: {error}",
|
||||
stringify!($id)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
assert_deserialization!(AgentId);
|
||||
assert_deserialization!(ArtifactId);
|
||||
assert_deserialization!(NodeId);
|
||||
assert_deserialization!(ProcessId);
|
||||
assert_deserialization!(LaunchAttemptId);
|
||||
assert_deserialization!(ProjectId);
|
||||
assert_deserialization!(TaskDefinitionId);
|
||||
assert_deserialization!(TaskInstanceId);
|
||||
assert_deserialization!(TenantId);
|
||||
assert_deserialization!(UserId);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_tokens_are_bounded_without_using_identifier_syntax() {
|
||||
validate_opaque_token("opaque secret/+==", 64).unwrap();
|
||||
assert!(validate_opaque_token("", 64).is_err());
|
||||
assert!(validate_opaque_token("bad\0token", 64).is_err());
|
||||
assert!(validate_opaque_token(&"x".repeat(65), 64).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ pub use execution::{
|
|||
WasmTaskOutcome, WasmTaskResult, MAX_WASM_TASK_ENVELOPE_BYTES, WASM_TASK_ABI_VERSION,
|
||||
};
|
||||
pub use ids::{
|
||||
AgentId, ArtifactId, DebugSessionId, IdParseError, LaunchAttemptId, NodeId, ProcessId,
|
||||
ProjectId, RequestId, TaskDefinitionId, TaskInstanceId, TenantId, UserId,
|
||||
MAX_EXTERNAL_ID_BYTES,
|
||||
validate_opaque_token, AgentId, ArtifactId, DebugSessionId, IdParseError, LaunchAttemptId,
|
||||
NodeId, OpaqueTokenError, ProcessId, ProjectId, RequestId, TaskDefinitionId, TaskInstanceId,
|
||||
TenantId, UserId, MAX_EXTERNAL_ID_BYTES,
|
||||
};
|
||||
pub use limits::{
|
||||
LargeArgumentPolicy, LimitError, LimitKind, LogBuffer, LogRecord, ResourceLimits,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue