bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
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(),
    }
}