use super::*;
impl BucketWarden {
pub fn get_object_tagging(
&mut self,
principal: &str,
bucket: &str,
key: &str,
version_id: Option<&str>,
) -> Result<ObjectTaggingResult, RuntimeError> {
let resource = object_resource(bucket, key);
self.authorize(principal, S3Action::GetObjectTagging, &resource)?;
let version = if let Some(version_id) = version_id {
self.version_by_id(bucket, key, version_id)?
} else {
self.current_version(bucket, key)?
};
if version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
let result = ObjectTaggingResult {
bucket: bucket.to_string(),
key: key.to_string(),
version_id: version.version_id.clone(),
tags: version.tags.clone(),
};
self.audit_allowed(
principal,
S3Action::GetObjectTagging,
&resource,
Some(result.version_id.clone()),
);
Ok(result)
}
pub fn put_object_tagging(
&mut self,
principal: &str,
request: ObjectTaggingRequest,
) -> Result<ObjectTaggingResult, RuntimeError> {
let resource = object_resource(&request.bucket, &request.key);
self.authorize(principal, S3Action::PutObjectTagging, &resource)?;
validate_tags(&request.tags)?;
let version = if let Some(version_id) = request.version_id.as_deref() {
self.version_by_id_mut(&request.bucket, &request.key, version_id)?
} else {
self.current_version_mut(&request.bucket, &request.key)?
};
if version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
version.tags = request.tags;
let version_id = version.version_id.clone();
let tags = version.tags.clone();
let result = ObjectTaggingResult {
bucket: request.bucket,
key: request.key,
version_id,
tags,
};
self.audit_allowed(
principal,
S3Action::PutObjectTagging,
&resource,
Some(result.version_id.clone()),
);
Ok(result)
}
pub fn bulk_put_object_tagging(
&mut self,
principal: &str,
request: BulkObjectTaggingRequest,
) -> BulkObjectTaggingResult {
let mut updated = Vec::new();
let mut errors = Vec::new();
for entry in request.entries {
let bucket = entry.bucket.clone();
let key = entry.key.clone();
let version_id = entry.version_id.clone();
match self.put_object_tagging(principal, entry) {
Ok(result) => updated.push(result),
Err(error) => {
errors.push(bulk_object_action_error(bucket, key, version_id, &error))
}
}
}
BulkObjectTaggingResult { updated, errors }
}
pub fn delete_object_tagging(
&mut self,
principal: &str,
bucket: &str,
key: &str,
version_id: Option<&str>,
) -> Result<ObjectTaggingResult, RuntimeError> {
let resource = object_resource(bucket, key);
self.authorize(principal, S3Action::DeleteObjectTagging, &resource)?;
let version = if let Some(version_id) = version_id {
self.version_by_id_mut(bucket, key, version_id)?
} else {
self.current_version_mut(bucket, key)?
};
if version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
let version_id = version.version_id.clone();
version.tags.clear();
let result = ObjectTaggingResult {
bucket: bucket.to_string(),
key: key.to_string(),
version_id,
tags: BTreeMap::new(),
};
self.audit_allowed(
principal,
S3Action::DeleteObjectTagging,
&resource,
Some(result.version_id.clone()),
);
Ok(result)
}
pub fn get_object_legal_hold(
&mut self,
principal: &str,
bucket: &str,
key: &str,
version_id: Option<&str>,
) -> Result<ObjectLegalHoldResult, RuntimeError> {
self.require_object_lock_enabled(bucket)?;
let resource = object_resource(bucket, key);
self.authorize(principal, S3Action::GetObjectLegalHold, &resource)?;
let version = if let Some(version_id) = version_id {
self.version_by_id(bucket, key, version_id)?
} else {
self.current_version(bucket, key)?
};
if version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
let result = ObjectLegalHoldResult {
bucket: bucket.to_string(),
key: key.to_string(),
version_id: version.version_id.clone(),
enabled: version.lock.legal_hold,
};
self.audit_allowed(
principal,
S3Action::GetObjectLegalHold,
&resource,
Some(result.version_id.clone()),
);
Ok(result)
}
pub fn put_object_legal_hold(
&mut self,
principal: &str,
request: ObjectLegalHoldRequest,
) -> Result<ObjectLegalHoldResult, RuntimeError> {
self.require_object_lock_enabled(&request.bucket)?;
let resource = object_resource(&request.bucket, &request.key);
self.authorize(principal, S3Action::PutObjectLegalHold, &resource)?;
let version = if let Some(version_id) = request.version_id.as_deref() {
self.version_by_id_mut(&request.bucket, &request.key, version_id)?
} else {
self.current_version_mut(&request.bucket, &request.key)?
};
if version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
version.lock.set_legal_hold(request.enabled);
let version_id = version.version_id.clone();
let result = ObjectLegalHoldResult {
bucket: request.bucket,
key: request.key,
version_id,
enabled: request.enabled,
};
self.audit_allowed(
principal,
S3Action::PutObjectLegalHold,
&resource,
Some(result.enabled.to_string()),
);
self.emit_notification_event(
"s3:ObjectLock:LegalHoldUpdated",
&result.bucket,
&result.key,
&result.version_id,
);
Ok(result)
}
pub fn bulk_put_object_legal_hold(
&mut self,
principal: &str,
request: BulkObjectLegalHoldRequest,
) -> BulkObjectLegalHoldResult {
let mut updated = Vec::new();
let mut errors = Vec::new();
for entry in request.entries {
let bucket = entry.bucket.clone();
let key = entry.key.clone();
let version_id = entry.version_id.clone();
match self.put_object_legal_hold(principal, entry) {
Ok(result) => updated.push(result),
Err(error) => {
errors.push(bulk_object_action_error(bucket, key, version_id, &error))
}
}
}
BulkObjectLegalHoldResult { updated, errors }
}
pub fn get_object_retention(
&mut self,
principal: &str,
bucket: &str,
key: &str,
version_id: Option<&str>,
) -> Result<ObjectRetentionResult, RuntimeError> {
self.require_object_lock_enabled(bucket)?;
let resource = object_resource(bucket, key);
self.authorize(principal, S3Action::GetObjectRetention, &resource)?;
let version = if let Some(version_id) = version_id {
self.version_by_id(bucket, key, version_id)?
} else {
self.current_version(bucket, key)?
};
if version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
let result = ObjectRetentionResult {
bucket: bucket.to_string(),
key: key.to_string(),
version_id: version.version_id.clone(),
mode: version
.lock
.retention_mode
.map(retention_mode_text)
.map(str::to_string),
retain_until_epoch_seconds: version.lock.retain_until_epoch_seconds,
};
self.audit_allowed(
principal,
S3Action::GetObjectRetention,
&resource,
Some(result.version_id.clone()),
);
Ok(result)
}
pub fn put_object_retention(
&mut self,
principal: &str,
request: ObjectRetentionRequest,
) -> Result<ObjectRetentionResult, RuntimeError> {
self.require_object_lock_enabled(&request.bucket)?;
let resource = object_resource(&request.bucket, &request.key);
self.authorize(principal, S3Action::PutObjectRetention, &resource)?;
self.authorize_bypass_governance_if_requested(
principal,
&resource,
request.bypass_governance,
)?;
let mode = retention_mode_from_text(&request.mode)?;
self.require_retain_until_in_future(request.retain_until_epoch_seconds)?;
let current_version = if let Some(version_id) = request.version_id.as_deref() {
self.version_by_id(&request.bucket, &request.key, version_id)?
} else {
self.current_version(&request.bucket, &request.key)?
};
if current_version.delete_marker {
return Err(RuntimeError::NoSuchKey(resource.clone()));
}
if let Err(error) = current_version.lock.assert_retention_updatable(
mode,
request.retain_until_epoch_seconds,
request.bypass_governance,
) {
self.audit_denied(
principal,
S3Action::PutObjectRetention,
&resource,
Some(error.to_string()),
);
return Err(RuntimeError::ObjectLocked(error));
}
let version = if let Some(version_id) = request.version_id.as_deref() {
self.version_by_id_mut(&request.bucket, &request.key, version_id)?
} else {
self.current_version_mut(&request.bucket, &request.key)?
};
version
.lock
.set_retention(mode, request.retain_until_epoch_seconds);
let version_id = version.version_id.clone();
let result = ObjectRetentionResult {
bucket: request.bucket,
key: request.key,
version_id,
mode: Some(retention_mode_text(mode).to_string()),
retain_until_epoch_seconds: Some(request.retain_until_epoch_seconds),
};
self.audit_allowed(
principal,
S3Action::PutObjectRetention,
&resource,
result
.retain_until_epoch_seconds
.map(|value| value.to_string()),
);
self.emit_notification_event(
"s3:ObjectLock:RetentionUpdated",
&result.bucket,
&result.key,
&result.version_id,
);
Ok(result)
}
pub fn bulk_put_object_retention(
&mut self,
principal: &str,
request: BulkObjectRetentionRequest,
) -> BulkObjectRetentionResult {
let mut updated = Vec::new();
let mut errors = Vec::new();
for entry in request.entries {
let bucket = entry.bucket.clone();
let key = entry.key.clone();
let version_id = entry.version_id.clone();
match self.put_object_retention(principal, entry) {
Ok(result) => updated.push(result),
Err(error) => {
errors.push(bulk_object_action_error(bucket, key, version_id, &error))
}
}
}
BulkObjectRetentionResult { updated, errors }
}
}
fn bulk_object_action_error(
bucket: String,
key: String,
version_id: Option<String>,
error: &RuntimeError,
) -> BulkObjectActionError {
BulkObjectActionError {
bucket,
key,
version_id,
code: multi_object_delete_error_code(error).to_string(),
message: error.to_string(),
}
}