Phase 7 Task 3: 数据安全与合规

- 创建 security_manager.py 安全模块
  - SecurityManager: 安全管理主类
  - 审计日志系统 - 记录所有数据操作
  - 端到端加密 - AES-256-GCM 加密项目数据
  - 数据脱敏 - 支持手机号、邮箱、身份证等敏感信息脱敏
  - 数据访问策略 - 基于用户、角色、IP、时间的访问控制
  - 访问审批流程 - 敏感数据访问需要审批

- 更新 schema.sql 添加安全相关数据库表
  - audit_logs: 审计日志表
  - encryption_configs: 加密配置表
  - masking_rules: 脱敏规则表
  - data_access_policies: 数据访问策略表
  - access_requests: 访问请求表

- 更新 main.py 添加安全相关 API 端点
  - GET /api/v1/audit-logs - 查询审计日志
  - GET /api/v1/audit-logs/stats - 审计统计
  - POST /api/v1/projects/{id}/encryption/enable - 启用加密
  - POST /api/v1/projects/{id}/encryption/disable - 禁用加密
  - POST /api/v1/projects/{id}/encryption/verify - 验证密码
  - GET /api/v1/projects/{id}/encryption - 获取加密配置
  - POST /api/v1/projects/{id}/masking-rules - 创建脱敏规则
  - GET /api/v1/projects/{id}/masking-rules - 获取脱敏规则
  - PUT /api/v1/masking-rules/{id} - 更新脱敏规则
  - DELETE /api/v1/masking-rules/{id} - 删除脱敏规则
  - POST /api/v1/projects/{id}/masking/apply - 应用脱敏
  - POST /api/v1/projects/{id}/access-policies - 创建访问策略
  - GET /api/v1/projects/{id}/access-policies - 获取访问策略
  - POST /api/v1/access-policies/{id}/check - 检查访问权限
  - POST /api/v1/access-requests - 创建访问请求
  - POST /api/v1/access-requests/{id}/approve - 批准访问
  - POST /api/v1/access-requests/{id}/reject - 拒绝访问

- 更新 requirements.txt 添加 cryptography 依赖

- 更新 STATUS.md 和 README.md 记录完成状态
This commit is contained in:
OpenClaw Bot
2026-02-23 18:11:11 +08:00
parent 847e183b85
commit 95a558acc9
19 changed files with 4407 additions and 1646 deletions

View File

@@ -1,204 +1,197 @@
// InsightFlow Chrome Extension - Content Script
// 在页面中注入,处理页面交互
// 在网页上下文中运行,负责提取页面内容
(function() {
'use strict';
// 防止重复注入
if (window.insightflowInjected) return;
window.insightflowInjected = true;
// 创建浮动按钮
let floatingButton = null;
let selectionPopup = null;
// 监听选中文本
document.addEventListener('mouseup', handleSelection);
document.addEventListener('keyup', handleSelection);
function handleSelection(e) {
const selection = window.getSelection();
const text = selection.toString().trim();
if (text.length > 0) {
showFloatingButton(selection);
} else {
hideFloatingButton();
hideSelectionPopup();
'use strict';
// 避免重复注入
if (window.insightFlowInjected) return;
window.insightFlowInjected = true;
// 提取页面主要内容
function extractContent() {
const result = {
url: window.location.href,
title: document.title,
content: '',
html: document.documentElement.outerHTML,
meta: {
author: getMetaContent('author'),
description: getMetaContent('description'),
keywords: getMetaContent('keywords'),
publishedTime: getMetaContent('article:published_time') || getMetaContent('publishedDate'),
siteName: getMetaContent('og:site_name') || getMetaContent('application-name'),
language: document.documentElement.lang || 'unknown'
},
extractedAt: new Date().toISOString()
};
// 尝试提取正文内容
const article = extractArticleContent();
result.content = article.text;
result.contentHtml = article.html;
result.wordCount = article.text.split(/\s+/).length;
return result;
}
}
// 显示浮动按钮
function showFloatingButton(selection) {
if (!floatingButton) {
floatingButton = document.createElement('div');
floatingButton.className = 'insightflow-float-btn';
floatingButton.innerHTML = `
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 5v14M5 12h14"/>
</svg>
`;
floatingButton.title = '保存到 InsightFlow';
document.body.appendChild(floatingButton);
floatingButton.addEventListener('click', () => {
const text = window.getSelection().toString().trim();
if (text) {
showSelectionPopup(text);
// 获取 meta 标签内容
function getMetaContent(name) {
const meta = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
return meta ? meta.getAttribute('content') : '';
}
// 提取文章正文(使用多种策略)
function extractArticleContent() {
// 策略1使用 Readability 算法(简化版)
let bestElement = findBestElement();
if (bestElement) {
return {
text: cleanText(bestElement.innerText),
html: bestElement.innerHTML
};
}
});
// 策略2回退到 body 内容
const body = document.body;
return {
text: cleanText(body.innerText),
html: body.innerHTML
};
}
// 定位按钮
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
floatingButton.style.left = `${rect.right + window.scrollX - 40}px`;
floatingButton.style.top = `${rect.top + window.scrollY - 45}px`;
floatingButton.style.display = 'flex';
}
// 隐藏浮动按钮
function hideFloatingButton() {
if (floatingButton) {
floatingButton.style.display = 'none';
}
}
// 显示选择弹窗
function showSelectionPopup(text) {
hideFloatingButton();
if (!selectionPopup) {
selectionPopup = document.createElement('div');
selectionPopup.className = 'insightflow-popup';
document.body.appendChild(selectionPopup);
}
selectionPopup.innerHTML = `
<div class="insightflow-popup-header">
<span>保存到 InsightFlow</span>
<button class="insightflow-close-btn">&times;</button>
</div>
<div class="insightflow-popup-content">
<div class="insightflow-text-preview">${escapeHtml(text.substring(0, 200))}${text.length > 200 ? '...' : ''}</div>
<div class="insightflow-actions">
<button class="insightflow-btn insightflow-btn-primary" id="if-save-quick">快速保存</button>
<button class="insightflow-btn" id="if-save-select">选择项目...</button>
</div>
</div>
`;
selectionPopup.style.display = 'block';
// 定位弹窗
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
selectionPopup.style.left = `${Math.min(rect.left + window.scrollX, window.innerWidth - 320)}px`;
selectionPopup.style.top = `${rect.bottom + window.scrollY + 10}px`;
// 绑定事件
selectionPopup.querySelector('.insightflow-close-btn').addEventListener('click', hideSelectionPopup);
selectionPopup.querySelector('#if-save-quick').addEventListener('click', () => saveQuick(text));
selectionPopup.querySelector('#if-save-select').addEventListener('click', () => saveWithProject(text));
}
// 隐藏选择弹窗
function hideSelectionPopup() {
if (selectionPopup) {
selectionPopup.style.display = 'none';
}
}
// 快速保存
async function saveQuick(text) {
hideSelectionPopup();
chrome.runtime.sendMessage({
action: 'clipPage',
selectionText: text
});
}
// 选择项目保存
async function saveWithProject(text) {
// 获取项目列表
chrome.runtime.sendMessage({ action: 'fetchProjects' }, (response) => {
if (response.success && response.projects.length > 0) {
showProjectSelector(text, response.projects);
} else {
saveQuick(text); // 失败时快速保存
}
});
}
// 显示项目选择器
function showProjectSelector(text, projects) {
selectionPopup.innerHTML = `
<div class="insightflow-popup-header">
<span>选择项目</span>
<button class="insightflow-close-btn">&times;</button>
</div>
<div class="insightflow-popup-content">
<div class="insightflow-project-list">
${projects.map(p => `
<div class="insightflow-project-item" data-id="${p.id}">
<div class="insightflow-project-name">${escapeHtml(p.name)}</div>
<div class="insightflow-project-desc">${escapeHtml(p.description || '').substring(0, 50)}</div>
</div>
`).join('')}
</div>
</div>
`;
selectionPopup.querySelector('.insightflow-close-btn').addEventListener('click', hideSelectionPopup);
// 绑定项目选择事件
selectionPopup.querySelectorAll('.insightflow-project-item').forEach(item => {
item.addEventListener('click', () => {
const projectId = item.dataset.id;
saveToProject(text, projectId);
});
});
}
// 保存到指定项目
async function saveToProject(text, projectId) {
hideSelectionPopup();
chrome.runtime.sendMessage({
action: 'getConfig'
}, (config) => {
// 临时设置默认项目
config.defaultProjectId = projectId;
chrome.runtime.sendMessage({
action: 'saveConfig',
config: config
}, () => {
chrome.runtime.sendMessage({
action: 'clipPage',
selectionText: text
// 查找最佳内容元素(基于文本密度)
function findBestElement() {
const candidates = [];
const elements = document.querySelectorAll('article, [role="main"], .post-content, .entry-content, .article-content, #content, .content');
elements.forEach(el => {
const text = el.innerText || '';
const linkDensity = calculateLinkDensity(el);
const textDensity = text.length / (el.innerHTML.length || 1);
candidates.push({
element: el,
score: text.length * textDensity * (1 - linkDensity),
textLength: text.length
});
});
});
});
}
// HTML 转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 点击页面其他地方关闭弹窗
document.addEventListener('click', (e) => {
if (selectionPopup && !selectionPopup.contains(e.target) &&
floatingButton && !floatingButton.contains(e.target)) {
hideSelectionPopup();
hideFloatingButton();
// 按分数排序
candidates.sort((a, b) => b.score - a.score);
return candidates.length > 0 ? candidates[0].element : null;
}
});
// 计算链接密度
function calculateLinkDensity(element) {
const links = element.getElementsByTagName('a');
let linkLength = 0;
for (let link of links) {
linkLength += link.innerText.length;
}
const textLength = element.innerText.length || 1;
return linkLength / textLength;
}
// 清理文本
function cleanText(text) {
return text
.replace(/\s+/g, ' ')
.replace(/\n\s*\n/g, '\n\n')
.trim();
}
// 高亮选中的文本
function highlightSelection() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const selectedText = selection.toString().trim();
if (selectedText.length > 0) {
return {
text: selectedText,
context: getSelectionContext(range)
};
}
}
return null;
}
// 获取选中内容的上下文
function getSelectionContext(range) {
const container = range.commonAncestorContainer;
const element = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
return {
tagName: element.tagName,
className: element.className,
id: element.id,
surroundingText: element.innerText.substring(0, 200)
};
}
// 监听来自 background 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'extractContent') {
const content = extractContent();
sendResponse({ success: true, data: content });
} else if (request.action === 'getSelection') {
const selection = highlightSelection();
sendResponse({ success: true, data: selection });
} else if (request.action === 'ping') {
sendResponse({ success: true, pong: true });
}
return true;
});
// 添加浮动按钮(可选)
function addFloatingButton() {
const button = document.createElement('div');
button.id = 'insightflow-clipper-btn';
button.innerHTML = '📎';
button.title = 'Clip to InsightFlow';
button.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: #4CAF50;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 999999;
font-size: 24px;
transition: transform 0.2s;
`;
button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.1)';
});
button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)';
});
button.addEventListener('click', () => {
chrome.runtime.sendMessage({ action: 'openClipper' });
});
document.body.appendChild(button);
}
// 如果启用,添加浮动按钮
chrome.storage.sync.get(['showFloatingButton'], (result) => {
if (result.showFloatingButton !== false) {
addFloatingButton();
}
});
console.log('[InsightFlow] Content script loaded');
})();