use openlark_core::error::{
authentication_error, business_error, configuration_error, network_error_with_details,
permission_missing_error, rate_limit_error, token_expired_error, validation_error, CoreError,
ErrorCode, ErrorContext, ErrorTrait,
};
use serde::Serialize;
use std::time::Duration;
use openlark_core::error::ApiError;
pub type SecurityError = CoreError;
pub type SecurityResult<T> = Result<T, SecurityError>;
#[derive(Debug, Copy, Clone)]
pub struct SecurityErrorBuilder;
impl SecurityErrorBuilder {
pub fn device_not_found(device_id: impl Into<String>) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("device_id", device_id.into());
ctx.add_context("operation", "device_lookup");
CoreError::Validation {
field: "device_id".into(),
message: "设备未找到,请检查设备ID是否正确".to_string(),
code: ErrorCode::ValidationError,
ctx: Box::new(ctx),
}
}
pub fn device_connection_failed(
device_id: impl Into<String>,
reason: impl Into<String>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("device_id", device_id.into());
ctx.add_context("connection_reason", reason.into());
ctx.add_context("operation", "device_connection");
network_error_with_details(
"设备连接失败",
None::<String>,
Some(format!(
"device:{}",
ctx.get_context("device_id").unwrap_or_default()
)),
)
}
pub fn device_temporarily_unavailable(
device_id: impl Into<String>,
retry_after: Option<Duration>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("device_id", device_id.into());
ctx.add_context("availability", "temporary");
CoreError::ServiceUnavailable {
service: "security_device".into(),
retry_after,
code: ErrorCode::ServiceUnavailable,
ctx: Box::new(ctx),
}
}
pub fn access_denied(resource: impl Into<String>, reason: impl Into<String>) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("resource", resource.into());
ctx.add_context("deny_reason", reason.into());
ctx.add_context("operation", "access_control");
permission_missing_error(&["security:access"])
}
pub fn insufficient_permissions(
required_permissions: &[impl AsRef<str>],
current_permissions: &[impl AsRef<str>],
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context(
"required_permissions",
required_permissions
.iter()
.map(|s| s.as_ref())
.collect::<Vec<_>>()
.join(","),
);
ctx.add_context(
"current_permissions",
current_permissions
.iter()
.map(|s| s.as_ref())
.collect::<Vec<_>>()
.join(","),
);
CoreError::Authentication {
message: "安全权限不足".to_string(),
code: ErrorCode::PermissionMissing,
ctx: Box::new(ctx),
}
}
pub fn face_recognition_failed(
reason: impl Into<String>,
image_id: Option<impl Into<String>>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("recognition_reason", reason.into());
if let Some(id) = image_id {
ctx.add_context("image_id", id.into());
}
ctx.add_context("operation", "face_recognition");
validation_error("face_image", "人脸识别失败,请重新上传清晰的人脸照片")
}
pub fn face_recognition_service_unavailable() -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("service", "face_recognition");
ctx.add_context("operation", "face_recognition");
CoreError::ServiceUnavailable {
service: "face_recognition".into(),
retry_after: Some(Duration::from_secs(30)),
code: ErrorCode::ServiceUnavailable,
ctx: Box::new(ctx),
}
}
pub fn visitor_permission_expired(
visitor_id: impl Into<String>,
visit_type: impl Into<String>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("visitor_id", visitor_id.into());
ctx.add_context("visit_type", visit_type.into());
ctx.add_context("operation", "visitor_access");
business_error("访客权限已过期,请重新申请")
}
pub fn visitor_authentication_failed(
visitor_id: impl Into<String>,
reason: impl Into<String>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("visitor_id", visitor_id.into());
ctx.add_context("auth_reason", reason.into());
ctx.add_context("operation", "visitor_authentication");
authentication_error("访客身份验证失败")
}
pub fn compliance_check_failed(
compliance_type: impl Into<String>,
reason: impl Into<String>,
resource_id: Option<impl Into<String>>,
) -> SecurityError {
let compliance_type_str = compliance_type.into();
let reason_str = reason.into();
let mut ctx = ErrorContext::new();
ctx.add_context("compliance_type", compliance_type_str);
ctx.add_context("violation_reason", reason_str.clone());
if let Some(id) = resource_id {
ctx.add_context("resource_id", id.into());
}
ctx.add_context("operation", "compliance_check");
CoreError::Business {
message: format!("合规检查失败: {}", reason_str),
code: ErrorCode::BusinessError,
ctx: Box::new(ctx),
}
}
pub fn audit_log_failed(
log_type: impl Into<String>,
reason: impl Into<String>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("log_type", log_type.into());
ctx.add_context("failure_reason", reason.into());
ctx.add_context("operation", "audit_logging");
CoreError::Internal {
code: ErrorCode::InternalError,
message: "审计日志写入失败".to_string(),
source: None,
ctx: Box::new(ctx),
}
}
pub fn security_config_invalid(
config_key: impl Into<String>,
reason: impl Into<String>,
) -> SecurityError {
let config_key_str = config_key.into();
let reason_str = reason.into();
let mut ctx = ErrorContext::new();
ctx.add_context("config_key", config_key_str.clone());
ctx.add_context("error_reason", reason_str.clone());
ctx.add_context("operation", "security_config");
configuration_error(format!(
"安全配置参数 {} 无效: {}",
config_key_str, reason_str
))
}
pub fn time_sync_failed(service: impl Into<String>, deviation_ms: i64) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("sync_service", service.into());
ctx.add_context("time_deviation_ms", deviation_ms.to_string());
ctx.add_context("operation", "time_sync");
business_error("时间同步失败,安全验证需要精确的时间同步")
}
pub fn crypto_operation_failed(
operation: impl Into<String>,
algorithm: impl Into<String>,
reason: impl Into<String>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("crypto_operation", operation.into());
ctx.add_context("algorithm", algorithm.into());
ctx.add_context("failure_reason", reason.into());
ctx.add_context("operation", "cryptography");
CoreError::Internal {
code: ErrorCode::InternalError,
message: "加密操作失败".to_string(),
source: None,
ctx: Box::new(ctx),
}
}
pub fn security_check_timeout(
check_type: impl Into<String>,
timeout_duration: Duration,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("check_type", check_type.into());
ctx.add_context(
"timeout_duration_ms",
timeout_duration.as_millis().to_string(),
);
ctx.add_context("operation", "security_check");
CoreError::Timeout {
duration: timeout_duration,
operation: Some(format!(
"security_check:{}",
ctx.get_context("check_type").unwrap_or_default()
)),
ctx: Box::new(ctx),
}
}
pub fn security_api_rate_limited(
endpoint: impl Into<String>,
limit: u32,
window_seconds: u64,
) -> SecurityError {
let mut ctx = ErrorContext::new();
ctx.add_context("api_endpoint", endpoint.into());
ctx.add_context("rate_limit", limit.to_string());
ctx.add_context("window_seconds", window_seconds.to_string());
rate_limit_error(limit, Duration::from_secs(window_seconds), None)
}
}
pub fn map_feishu_security_error(
feishu_code: i32,
message: &str,
request_id: Option<&str>,
) -> SecurityError {
let mut ctx = ErrorContext::new();
if let Some(req_id) = request_id {
ctx.set_request_id(req_id);
}
ctx.add_context("feishu_code", feishu_code.to_string());
ctx.add_context("service", "security");
match ErrorCode::from_feishu_code(feishu_code) {
Some(ErrorCode::PermissionMissing) => CoreError::Authentication {
message: format!("安全权限不足: {}", message),
code: ErrorCode::PermissionMissing,
ctx: Box::new(ctx),
},
Some(ErrorCode::AccessTokenExpiredV2) => {
token_expired_error(format!("安全访问令牌已过期: {}", message))
}
Some(ErrorCode::ValidationError) => validation_error("security_parameter", message),
Some(ErrorCode::BusinessError) => {
SecurityErrorBuilder::compliance_check_failed("business_rule", message, None::<String>)
}
_ => {
CoreError::Api(Box::new(ApiError {
status: feishu_code as u16,
endpoint: "security".into(),
message: message.to_string(),
source: None,
code: ErrorCode::from_feishu_code(feishu_code).unwrap_or(ErrorCode::InternalError),
ctx: Box::new(ctx),
}))
}
}
}
pub trait SecurityErrorExt {
fn is_device_error(&self) -> bool;
fn is_permission_error(&self) -> bool;
fn is_compliance_error(&self) -> bool;
fn is_authentication_error(&self) -> bool;
fn security_operation(&self) -> Option<&str>;
fn affected_resource_id(&self) -> Option<&str>;
fn to_security_event(&self) -> SecurityEvent;
}
impl SecurityErrorExt for SecurityError {
fn is_device_error(&self) -> bool {
self.context().get_context("device_id").is_some()
}
fn is_permission_error(&self) -> bool {
matches!(
self,
CoreError::Authentication {
code: ErrorCode::PermissionMissing,
..
}
)
}
fn is_compliance_error(&self) -> bool {
self.context().get_context("compliance_type").is_some()
}
fn is_authentication_error(&self) -> bool {
matches!(self, CoreError::Authentication { .. })
}
fn security_operation(&self) -> Option<&str> {
match self.context().get_context("operation") {
Some(s) => Some(s),
None => None,
}
}
fn affected_resource_id(&self) -> Option<&str> {
match self
.context()
.get_context("device_id")
.or_else(|| self.context().get_context("visitor_id"))
{
Some(s) => Some(s),
None => None,
}
}
fn to_security_event(&self) -> SecurityEvent {
SecurityEvent {
event_id: uuid::Uuid::new_v4().to_string(),
timestamp: chrono::Utc::now(),
event_type: "security_error".to_string(),
severity: "medium".to_string(),
operation: self.security_operation().unwrap_or("unknown").to_string(),
resource_id: self.affected_resource_id().map(|s| s.to_string()),
error_code: ErrorCode::InternalError,
message: "安全错误".to_string(),
context: serde_json::json!({}),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SecurityEvent {
pub event_id: String,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub event_type: String,
pub severity: String,
pub operation: String,
pub resource_id: Option<String>,
pub error_code: ErrorCode,
pub message: String,
pub context: serde_json::Value,
}
#[derive(Debug, Clone, Copy)]
pub struct SecurityErrorAnalyzer;
impl SecurityErrorAnalyzer {
pub fn analyze_security_risk(error: &SecurityError) -> SecurityRiskAssessment {
let risk_level = match error {
CoreError::Authentication { .. } if error.is_permission_error() => {
SecurityRiskLevel::High
}
CoreError::Business { .. } => SecurityRiskLevel::Critical,
CoreError::Internal { .. } => SecurityRiskLevel::High,
CoreError::Validation { .. } => SecurityRiskLevel::Medium,
_ => SecurityRiskLevel::Low,
};
let risk_type = if error.is_permission_error() {
SecurityRiskType::AccessControl
} else if error.is_compliance_error() {
SecurityRiskType::Compliance
} else if error.is_device_error() {
SecurityRiskType::DeviceSecurity
} else {
SecurityRiskType::General
};
SecurityRiskAssessment {
risk_level,
risk_type,
immediate_action: SecurityAction::LogAndMonitor,
escalation_required: matches!(
error,
CoreError::Business { .. } | CoreError::Internal { .. }
),
compliance_impact: ComplianceImpact::Low,
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
pub struct SecurityRiskAssessment {
pub risk_level: SecurityRiskLevel,
pub risk_type: SecurityRiskType,
pub immediate_action: SecurityAction,
pub escalation_required: bool,
pub compliance_impact: ComplianceImpact,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum SecurityRiskLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum SecurityRiskType {
AccessControl,
Authentication,
DeviceSecurity,
Compliance,
General,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum SecurityAction {
RevokeAccess,
InitiateInvestigation,
ActivateBackup,
LogAndMonitor,
BlockRequest,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum ComplianceImpact {
Low,
Medium,
High,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_error_creation() {
let error = SecurityErrorBuilder::device_not_found("device_123");
assert!(error.is_validation_error());
assert!(error.is_device_error());
}
#[test]
fn test_permission_error() {
let error = SecurityErrorBuilder::access_denied("admin_panel", "insufficient_role");
assert!(error.is_permission_error());
}
#[test]
fn test_compliance_error() {
let error = SecurityErrorBuilder::compliance_check_failed(
"gdpr",
"data_retention_violation",
Some("data_set_456"),
);
assert!(error.is_compliance_error());
}
#[test]
fn test_security_event_generation() {
let error = SecurityErrorBuilder::device_not_found("device_123");
let event = error.to_security_event();
assert_eq!(event.message, "安全错误");
}
#[test]
fn test_feishu_error_mapping() {
let error = map_feishu_security_error(99991672, "权限不足", Some("req_123"));
assert!(error.is_permission_error());
}
#[test]
fn test_security_risk_assessment() {
let error = SecurityErrorBuilder::access_denied("secure_area", "no_clearance");
let assessment = SecurityErrorAnalyzer::analyze_security_risk(&error);
assert!(matches!(assessment.risk_level, SecurityRiskLevel::High));
}
}