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:
@@ -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">×</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">×</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');
|
||||
})();
|
||||
Reference in New Issue
Block a user