- 创建 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 记录完成状态
197 lines
6.4 KiB
JavaScript
197 lines
6.4 KiB
JavaScript
// InsightFlow Chrome Extension - Content Script
|
||
// 在网页上下文中运行,负责提取页面内容
|
||
|
||
(function() {
|
||
'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;
|
||
}
|
||
|
||
// 获取 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
|
||
};
|
||
}
|
||
|
||
// 查找最佳内容元素(基于文本密度)
|
||
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
|
||
});
|
||
});
|
||
|
||
// 按分数排序
|
||
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');
|
||
})(); |