apache / apache/linkis

[Feature][Filesystem] Add permission control to resultsetsToExcel download interface

Open
#5,309 1 comment 0 reactions 0 assignees View on GitHub
feature
Dominant language
Java
Stars
3.4k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Description

### Linkis Component

linkis-public-enhancements/linkis-ps-publicservice

### What happened

**English:**

The `resultsetsToExcel` interface for downloading multiple result sets lacks permission control, allowing unauthorized users to download result sets, while the single result set download (`resultsetToExcel`) has proper permission control.

**Problem Description:**
1. Single result set download interface has correct permission control - unauthorized users receive error messages
2. Multiple result sets download interface (`resultsetsToExcel`) lacks permission control - unauthorized users can successfully download
3. Need to unify permission control logic to ensure all result set download interfaces have appropriate permission checks

**Security Impact:**
- Unauthorized data access
- Data leakage risk
- Inconsistent security model
- Compliance violations

---

**中文:**

多个结果集下载接口`resultsetsToExcel`缺少权限控制,导致无权限用户也能下载结果集,而单个结果集下载(`resultsetToExcel`)则有正确的权限控制。

**问题描述:**
1. 单个结果集下载接口有正确的权限控制,无权限用户会收到错误提示
2. 多个结果集下载接口resultsetsToExcel缺少权限控制,无权限用户也能成功下载
3. 需要统一权限控制逻辑,确保所有结果集下载接口都有适当的权限检查

**安全影响:**
- 未经授权的数据访问
- 数据泄漏风险
- 不一致的安全模型
- 违反合规性

### What you expected to happen

**English:**

All result set download interfaces should have consistent permission control:

1. **Add permission check to `resultsetsToExcel`** matching `resultsetToExcel` interface
2. **Verify permissions for each result set file** before allowing download
3. **Unified error message format** for permission denials
4. **Comprehensive testing** to ensure permission control works correctly

**Expected Behavior:**
- Only users with appropriate permissions can download result sets
- Clear error messages for unauthorized access attempts
- Consistent permission model across all download endpoints
- Audit logging for all download attempts

---

**中文:**

所有结果集下载接口应该有一致的权限控制:

1. **向`resultsetsToExcel`添加权限检查**,与`resultsetToExcel`接口匹配
2. **在允许下载之前验证每个结果集文件的权限**
3. **统一的权限拒绝错误消息格式**
4. **全面测试**以确保权限控制正确工作

**期望行为:**
- 只有具有适当权限的用户才能下载结果集
- 未经授权访问尝试的清晰错误消息
- 所有下载端点的一致权限模型
- 所有下载尝试的审计日志

### How to reproduce

**English:**

**Test Case 1: Single result set download (works correctly)**
1. Login as User A
2. Execute a query as User B (different user)
3. Try to download User B's result set using `resultsetToExcel`
4. ✅ Receive permission denied error

**Test Case 2: Multiple result sets download (security bug)**
1. Login as User A
2. User B executes multiple queries
3. Try to download User B's result sets using `resultsetsToExcel`
4. ❌ Download succeeds without permission check

---

**中文:**

**测试用例1:单个结果集下载(正确工作)**
1. 以用户A登录
2. 以用户B(不同用户)执行查询
3. 尝试使用`resultsetToExcel`下载用户B的结果集
4. ✅ 收到权限拒绝错误

**测试用例2:多个结果集下载(安全漏洞)**
1. 以用户A登录
2. 用户B执行多个查询
3. 尝试使用`resultsetsToExcel`下载用户B的结果集
4. ❌ 下载成功,没有权限检查

### Anything else

**English:**

**Root Cause:**
```scala
// resultsetToExcel - Has permission check (correct)
def resultsetToExcel(resultSetPath: String, user: String): Response = {
// Check if user has permission to access this result set
if (!hasPermission(user, resultSetPath)) {
return Response.error("No permission to download this result set")
}

val data = readResultSet(resultSetPath)
exportToExcel(data)
}

// resultsetsToExcel - Missing permission check (bug)
def resultsetsToExcel(resultSetPaths: List[String], user: String): Response = {
// BUG: No permission check here!
val allData = resultSetPaths.flatMap(path => readResultSet(path))
exportToExcel(allData)
}
```

**Suggested Fix:**
```scala
// Fixed implementation
def resultsetsToExcel(resultSetPaths: List[String], user: String): Response = {
// Add permission check for each result set
val unauthorizedPaths = resultSetPaths.filterNot(path =>
hasPermission(user, path)
)

if (unauthorizedPaths.nonEmpty) {
return Response.error(
s"No permission to download result sets: ${unauthorizedPaths.mkString(", ")}"
)
}

// All permission checks passed, proceed with download
val allData = resultSetPaths.flatMap(path => readResultSet(path))
exportToExcel(allData)

// Log download action
auditLog.info(s"User $user downloaded ${resultSetPaths.size} result sets")
}
```

**Permission Check Implementation:**
```scala
def hasPermission(user: String, resultSetPath: String): Boolean = {
// Extract task ID from result set path
val taskId = extractTaskId(resultSetPath)

// Get task owner
val taskOwner = getTaskOwner(taskId)

// Permission rules:
// 1. Task owner can always access
// 2. Admin users can access all
// 3. Users with explicit sharing permission
user == taskOwner ||
isAdmin(user) ||
hasSharedPermission(user, taskId)
}
```

**Enhanced Security Features:**
```scala
// 1. Rate limiting for downloads
class DownloadRateLimiter {
private val limiter = RateLimiter.create(10.0) // 10 downloads per second

def checkLimit(user: String): Boolean = {
if (!limiter.tryAcquire()) {
auditLog.warn(s"Rate limit exceeded for user: $user")
return false
}
true
}
}

// 2. Audit logging
def auditDownload(user: String, resultSetPaths: List[String], success: Boolean): Unit = {
auditLog.info(s"""
|Download Attempt:
| User: $user
| Paths: ${resultSetPaths.mkString(", ")}
| Success: $success
| Timestamp: ${System.currentTimeMillis()}
""".stripMargin)
}

// 3. Data masking for sensitive fields
def maskSensitiveData(data: DataFrame, user: String): DataFrame = {
val sensitiveColumns = getSensitiveColumns(data)

sensitiveColumns.foldLeft(data) { (df, col) =>
if (!hasColumnPermission(user, col)) {
df.withColumn(col, lit("***"))
} else {
df
}
}
}
```

**Testing:**
```scala
class ResultSetDownloadSecurityTest {
test("resultsetsToExcel should check permissions") {
// Setup
val userA = "alice"
val userB = "bob"

// User B creates result sets
val resultSets = List(
executeQuery(userB, "SELECT * FROM table1"),
executeQuery(userB, "SELECT * FROM table2")
)

// User A tries to download User B's results
val response = resultsetsToExcel(resultSets.map(_.path), userA)

// Should fail with permission error
assert(response.isError)
assert(response.message.contains("No permission"))
}

test("admin can download all result sets") {
val admin = "admin"
val resultSets = List(/* any user's results */)

val response = resultsetsToExcel(resultSets.map(_.path), admin)

// Should succeed
assert(response.isSuccess)
}

test("download should be audited") {
val user = "alice"
val resultSets = List(/* user's own results */)

resultsetsToExcel(resultSets.map(_.path), user)

// Verify audit log entry
val logEntry = getLastAuditLog()
assert(logEntry.user == user)
assert(logEntry.action == "DOWNLOAD_RESULTSETS")
}
}
```

**Implementation Checklist:**
- [ ] Add permission check to `resultsetsToExcel` method
- [ ] Implement per-result-set permission validation
- [ ] Add audit logging for all downloads
- [ ] Implement rate limiting
- [ ] Add sensitive data masking
- [ ] Write comprehensive security tests
- [ ] Update API documentation
- [ ] Add monitoring alerts for unauthorized access attempts

---

**中文:**

**根本原因:**
```scala
// resultsetToExcel - 有权限检查(正确)
def resultsetToExcel(resultSetPath: String, user: String): Response = {
// 检查用户是否有权限访问此结果集
if (!hasPermission(user, resultSetPath)) {
return Response.error("无权限下载此结果集")
}

val data = readResultSet(resultSetPath)
exportToExcel(data)
}

// resultsetsToExcel - 缺少权限检查(bug)
def resultsetsToExcel(resultSetPaths: List[String], user: String): Response = {
// BUG: 这里没有权限检查!
val allData = resultSetPaths.flatMap(path => readResultSet(path))
exportToExcel(allData)
}
```

**建议修复:**
```scala
// 修复后的实现
def resultsetsToExcel(resultSetPaths: List[String], user: String): Response = {
// 为每个结果集添加权限检查
val unauthorizedPaths = resultSetPaths.filterNot(path =>
hasPermission(user, path)
)

if (unauthorizedPaths.nonEmpty) {
return Response.error(
s"无权限下载结果集: ${unauthorizedPaths.mkString(", ")}"
)
}

// 所有权限检查通过,继续下载
val allData = resultSetPaths.flatMap(path => readResultSet(path))
exportToExcel(allData)

// 记录下载操作
auditLog.info(s"用户 $user 下载了 ${resultSetPaths.size} 个结果集")
}
```

**权限检查实现:**
```scala
def hasPermission(user: String, resultSetPath: String): Boolean = {
// 从结果集路径提取任务ID
val taskId = extractTaskId(resultSetPath)

// 获取任务所有者
val taskOwner = getTaskOwner(taskId)

// 权限规则:
// 1. 任务所有者始终可以访问
// 2. 管理员用户可以访问所有
// 3. 具有显式共享权限的用户
user == taskOwner ||
isAdmin(user) ||
hasSharedPermission(user, taskId)
}
```

**增强的安全功能:**
```scala
// 1. 下载速率限制
class DownloadRateLimiter {
private val limiter = RateLimiter.create(10.0) // 每秒10次下载

def checkLimit(user: String): Boolean = {
if (!limiter.tryAcquire()) {
auditLog.warn(s"用户超过速率限制: $user")
return false
}
true
}
}

// 2. 审计日志
def auditDownload(user: String, resultSetPaths: List[String], success: Boolean): Unit = {
auditLog.info(s"""
|下载尝试:
| 用户: $user
| 路径: ${resultSetPaths.mkString(", ")}
| 成功: $success
| 时间戳: ${System.currentTimeMillis()}
""".stripMargin)
}

// 3. 敏感字段数据脱敏
def maskSensitiveData(data: DataFrame, user: String): DataFrame = {
val sensitiveColumns = getSensitiveColumns(data)

sensitiveColumns.foldLeft(data) { (df, col) =>
if (!hasColumnPermission(user, col)) {
df.withColumn(col, lit("***"))
} else {
df
}
}
}
```

**测试:**
```scala
class ResultSetDownloadSecurityTest {
test("resultsetsToExcel应该检查权限") {
// 设置
val userA = "alice"
val userB = "bob"

// 用户B创建结果集
val resultSets = List(
executeQuery(userB, "SELECT * FROM table1"),
executeQuery(userB, "SELECT * FROM table2")
)

// 用户A尝试下载用户B的结果
val response = resultsetsToExcel(resultSets.map(_.path), userA)

// 应该失败并返回权限错误
assert(response.isError)
assert(response.message.contains("无权限"))
}

test("管理员可以下载所有结果集") {
val admin = "admin"
val resultSets = List(/* 任何用户的结果 */)

val response = resultsetsToExcel(resultSets.map(_.path), admin)

// 应该成功
assert(response.isSuccess)
}

test("下载应该被审计") {
val user = "alice"
val resultSets = List(/* 用户自己的结果 */)

resultsetsToExcel(resultSets.map(_.path), user)

// 验证审计日志条目
val logEntry = getLastAuditLog()
assert(logEntry.user == user)
assert(logEntry.action == "DOWNLOAD_RESULTSETS")
}
}
```

**实施清单:**
- [ ] 向`resultsetsToExcel`方法添加权限检查
- [ ] 实现每个结果集的权限验证
- [ ] 为所有下载添加审计日志
- [ ] 实现速率限制
- [ ] 添加敏感数据脱敏
- [ ] 编写全面的安全测试
- [ ] 更新API文档
- [ ] 为未经授权的访问尝试添加监控警报

### Are you willing to submit a PR?

- [ ] Yes I am willing to submit a PR!

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.