156 lines
4.4 KiB
Markdown
156 lines
4.4 KiB
Markdown
|
|
# 任务010:安全修复
|
|||
|
|
|
|||
|
|
## 目标
|
|||
|
|
|
|||
|
|
修复代码审查发现的4个严重安全问题。
|
|||
|
|
|
|||
|
|
## 修复清单
|
|||
|
|
|
|||
|
|
### 1. SQL注入修复( 高优先级)
|
|||
|
|
|
|||
|
|
**问题位置:**
|
|||
|
|
- `src/routes/operation_logs.rs:49-63`
|
|||
|
|
- `src/routes/charge_records.rs:130`
|
|||
|
|
- `src/routes/energy_stats.rs:341`
|
|||
|
|
|
|||
|
|
**修复方案:**
|
|||
|
|
将所有 `format!` 拼接的SQL改为参数化查询。
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// 错误示例(operation_logs.rs)
|
|||
|
|
let sql = format!(
|
|||
|
|
"SELECT * FROM operation_logs WHERE action = '{}' AND created_at >= '{}'",
|
|||
|
|
action.replace("'", "''"), start_time
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 正确示例
|
|||
|
|
let sql = "SELECT * FROM operation_logs WHERE action = $1 AND created_at >= $2";
|
|||
|
|
sqlx::query_as(sql)
|
|||
|
|
.bind(&action)
|
|||
|
|
.bind(&start_time)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**涉及文件:**
|
|||
|
|
- `src/routes/operation_logs.rs`
|
|||
|
|
- `src/routes/charge_records.rs`
|
|||
|
|
- `src/routes/energy_stats.rs`
|
|||
|
|
- `src/routes/downloads.rs`
|
|||
|
|
|
|||
|
|
### 2. 密码哈希(🔴 高优先级)
|
|||
|
|
|
|||
|
|
**问题位置:** `src/routes/auth.rs:51`
|
|||
|
|
|
|||
|
|
**修复方案:**
|
|||
|
|
集成 `argon2` crate,密码存储和验证使用哈希。
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Cargo.toml 添加
|
|||
|
|
argon2 = "0.5"
|
|||
|
|
|
|||
|
|
// 密码哈希(创建/重置用户时)
|
|||
|
|
use argon2::{
|
|||
|
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, SaltString},
|
|||
|
|
Argon2,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
fn hash_password(password: &str) -> Result<String> {
|
|||
|
|
let salt = SaltString::generate(&mut OsRng);
|
|||
|
|
let argon2 = Argon2::default();
|
|||
|
|
let hash = argon2.hash_password(password.as_bytes(), &salt)?;
|
|||
|
|
Ok(hash.to_string())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 密码验证(登录时)
|
|||
|
|
fn verify_password(password: &str, hash: &str) -> Result<bool> {
|
|||
|
|
let parsed_hash = PasswordHash::new(hash)?;
|
|||
|
|
let argon2 = Argon2::default();
|
|||
|
|
Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok())
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**涉及文件:**
|
|||
|
|
- `src/routes/auth.rs` — 登录验证改用 `verify_password`
|
|||
|
|
- `src/routes/users.rs` — 创建/重置密码改用 `hash_password`
|
|||
|
|
- `src/db/migrate.rs` — users表password字段长度改为255(argon2哈希较长)
|
|||
|
|
|
|||
|
|
### 3. 组织级数据隔离(🔴 高优先级)
|
|||
|
|
|
|||
|
|
**问题位置:**
|
|||
|
|
- `src/routes/organizations.rs` 全部路由
|
|||
|
|
- `src/routes/charge_records.rs`
|
|||
|
|
- `src/routes/device_logs.rs`
|
|||
|
|
- `src/routes/energy_stats.rs`
|
|||
|
|
|
|||
|
|
**修复方案:**
|
|||
|
|
在 `CurrentUser` 中携带 `organization_id`,查询时自动过滤。
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// middleware/auth.rs 修改
|
|||
|
|
pub struct CurrentUser {
|
|||
|
|
pub user_id: i64,
|
|||
|
|
pub role_level: i32, // 0普通 1企业管理员 2总管理员
|
|||
|
|
pub organization_id: Option<i64>, // 新增
|
|||
|
|
pub permissions: HashSet<String>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询时根据角色自动过滤
|
|||
|
|
pub fn apply_org_filter(query: &str, user: &CurrentUser) -> String {
|
|||
|
|
if user.role_level >= 2 {
|
|||
|
|
// 总管理员看全部
|
|||
|
|
query.to_string()
|
|||
|
|
} else if let Some(org_id) = user.organization_id {
|
|||
|
|
// 企业管理员只看本组织
|
|||
|
|
format!("{} WHERE organization_id = {}", query, org_id)
|
|||
|
|
} else {
|
|||
|
|
format!("{} WHERE 1=0", query) // 无组织用户看不了
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**涉及文件:**
|
|||
|
|
- `src/middleware/auth.rs` — CurrentUser增加organization_id
|
|||
|
|
- `src/routes/organizations.rs` — 所有查询加组织过滤
|
|||
|
|
- `src/routes/charge_records.rs` — 加组织过滤
|
|||
|
|
- `src/routes/device_logs.rs` — 加组织过滤
|
|||
|
|
- `src/routes/energy_stats.rs` — 加组织过滤
|
|||
|
|
|
|||
|
|
### 4. 补充权限校验(🟡 中优先级)
|
|||
|
|
|
|||
|
|
**问题位置:**
|
|||
|
|
- `src/routes/charge_records.rs:158`
|
|||
|
|
- `src/routes/device_logs.rs:62`
|
|||
|
|
- `src/routes/energy_stats.rs:81-283`
|
|||
|
|
- `src/routes/downloads.rs:87`
|
|||
|
|
|
|||
|
|
**修复方案:**
|
|||
|
|
为每个路由添加对应的 `check_permission` 调用。
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// 示例
|
|||
|
|
#[get("/api/charge-records")]
|
|||
|
|
async fn list_charge_records(
|
|||
|
|
user: CurrentUser,
|
|||
|
|
pool: State<MySqlPool>,
|
|||
|
|
query: Query<ChargeRecordQuery>,
|
|||
|
|
) -> Result<Json<Vec<ChargeRecord>>> {
|
|||
|
|
auth::check_permission(&user, "charge_record:view")?; // 新增
|
|||
|
|
// ... 原有逻辑
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**权限码对应:**
|
|||
|
|
- 充电记录 → `charge_record:view`
|
|||
|
|
- 设备日志 → `device_log:view`
|
|||
|
|
- 能耗统计 → `energy:view`
|
|||
|
|
- 下载中心 → `charge_record:export` 或 `energy:export`
|
|||
|
|
|
|||
|
|
## 质量约束
|
|||
|
|
|
|||
|
|
1. 完成代码后执行 `cargo check` + `cargo clippy`,零报错零警告
|
|||
|
|
2. 分层拆分,单函数≤80行,命名语义化,完整注释
|
|||
|
|
3. 所有外部IO/网络请求异常捕获,禁止裸panic
|
|||
|
|
4. 分支逻辑全覆盖,不遗漏兜底分支
|
|||
|
|
5. 常量抽离,不使用废弃API
|
|||
|
|
6. Rust内存安全,合理管理所有权,禁用unsafe无合理理由
|
|||
|
|
7. 修复后重新运行审查清单中的安全项目,确保全部通过
|