hidekuma/flask-s3-viewer

GitHub: hidekuma/flask-s3-viewer

Flask S3 查看器是一个 Flask 扩展,用于在 Web 应用中便捷地浏览、上传和管理 Amazon S3 存储桶。

Stars: 13 | Forks: 4

![logo](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/5dd33e58b6004227.png) # Flask S3 查看器 从任何 Flask 应用程序浏览、上传和管理 Amazon S3 存储桶。 [![PyPI 版本](https://badge.fury.io/py/flask-s3-viewer.svg)](https://badge.fury.io/py/flask-s3-viewer) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/5186640653004230.svg)](https://github.com/hidekuma/flask-s3-viewer/actions/workflows/ci.yml) [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://www.python.org/) [![许可证:MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) ## 主要特性 - **现代 UI** — Tailwind CSS,HTMX 驱动的局部更新,明/暗模式,内联 heroicons。标题栏中的上级文件夹(`..`)行和已登录用户小部件。终端用户无需构建流程(CSS 已预构建)。 - **可选认证** — 钩子框架(`auth_callback` + `permission_callback`,包含按操作定义的常量),外加通过 `[auth]` 附加组件提供的内置 Google OAuth。认证路由(`/auth/{login,callback,logout}`)位于命名空间前缀之外,因此一个重定向 URI 即可覆盖应用中的所有查看器。 - **智能搜索** — 大小写不敏感的子字符串匹配,Unicode 安全(韩文/日文/带音调的拉丁文),NFC 规范化以确保 macOS 上传与浏览器 IME 匹配,搜索范围限定在当前文件夹内,并会显示匹配的子文件夹行。 - **默认安全** — 在每个前缀边界拒绝路径遍历令牌(`..`、`.`、`//`、`\`)。缓存目录通过 `realpath` 包容性检查阻止逃逸,缓存文件以 JSON 格式存储并设置严格的文件权限。 - **Flask 扩展模式** — `FlaskS3Viewer(app, namespace=...)` 自动注册。通过 `add_new_one(...)` 支持每个应用多个存储桶。支持 `init_app(app)` 进行延迟绑定。 - **多存储桶** — 独立的命名空间,可选的每个存储桶 CloudFront / 外部 `object_hostname`。 - **预签名上传** — 适用于大文件的多文件预签名 POST 流程;也支持默认的表单上传。 - **缓存** — 文件系统 JSON 缓存,带 TTL;写入时自动失效(搜索绕过缓存,已认证的列表按用户隔离)。 - **经过测试** — 203 个 pytest 用例,ruff + mypy 检查通过,基于 moto 的 S3 模拟。 ## 安装说明 ``` pip install flask_s3_viewer ``` 需要 Python 3.10+,Flask 3.0+,boto3 1.34+。 ## 快速开始 ``` from flask import Flask from flask_s3_viewer import FlaskS3Viewer from flask_s3_viewer.aws.ref import Region app = Flask(__name__) # 自动注册,无需调用 `register()`。 FlaskS3Viewer( app, namespace="my-bucket", object_hostname="https://cdn.example.com", # optional CloudFront host config={ "profile_name": "default", "region_name": Region.SEOUL.value, "bucket_name": "my-bucket", "cache_dir": "/tmp/flask_s3_viewer", "use_cache": True, "ttl": 86400, }, ) @app.route("/") def index(): return "App index" if __name__ == "__main__": app.run(debug=True, port=3000) ``` 访问 `http://localhost:3000/my-bucket/files` 以浏览存储桶。 ### 品牌标识(标题 + 标志) ``` FlaskS3Viewer( app, namespace="my-bucket", title="ACME File Vault", logo_path="/opt/acme/assets/logo.svg", # local file, auto inlined as a data: URI # or: logo_url="https://cdn.acme.io/logo.svg", logo_link_url="https://intranet.acme.io/dashboard", # optional (v1.3+) config={...}, ) ``` `logo_link_url` (v1.3+) 覆盖标题栏标志 + 标题锚点的点击目标。设置后,该锚点渲染为指向配置 URL 的普通 ``,默认的 HTMX 列表重置将被禁用——当品牌标识应将用户引导回外部仪表板/主页时很有用。省略则保留 v1.2 的就地 HTMX 交换。使用 `add_new_one` 时,省略以继承父级值,传递 `None` 以移除子命名空间上的父级覆盖,或传递不同的字符串以按命名空间覆盖。 ### 自定义模板(`template_folder`) 使用 CLI 脚手架生成可写的预置模板副本,编辑后,将查看器指向该文件夹: ``` # 仅使用模板(默认 — 涵盖大多数主题需求) flask_s3_viewer -p ./fsv-templates # 或者,fork 整个 UI 模板包(templates + static/css/app.css + htmx + core.js) flask_s3_viewer -p ./fsv-templates --with-static ``` ``` FlaskS3Viewer( app, namespace="my-bucket", template_folder="./fsv-templates", # files here win over bundled defaults config={...}, ) ``` 实际上,扩展通过 `ChoiceLoader` 将一个 `FileSystemLoader(template_folder)` 预置到应用的 Jinja 加载器中,因此任何未被覆盖的模板(例如当你只编辑了 `files.html` 时,`error.html`)仍会从预置模板解析。其他蓝图的模板解析不受影响。 ### 多存储桶 ``` viewer = FlaskS3Viewer(app, namespace="primary", config={...}) viewer.add_new_one(namespace="backups", config={...}) ``` 每个命名空间都有自己的 URL 前缀和自己的配置。 ### 延迟初始化 ``` viewer = FlaskS3Viewer(namespace="my-bucket", config={...}) def create_app(): app = Flask(__name__) viewer.init_app(app) return app ``` ### 访问底层 boto3 客户端 ``` from flask import current_app from flask_s3_viewer import FlaskS3Viewer # 在请求内部: client = current_app.extensions["flask_s3_viewer"]["my-bucket"]._s3 # 或通过辅助函数: client = FlaskS3Viewer.get_boto_client(app, "my-bucket") session = FlaskS3Viewer.get_boto_session(app, "my-bucket") ``` ## 配置 所有 `config` 键都会转发给底层的 S3 客户端: | 键 | 类型 | 默认值 | 说明 | |------------------|------------------|---------|------------------------------------------------| | `bucket_name` | str | — | 必需。 | | `profile_name` | str \| None | None | 如果为 None,则使用 boto3 默认凭证链。 | | `region_name` | str \| None | None | 例如 `ap-northeast-2`。 | | `endpoint_url` | str \| None | None | 自定义 S3 端点(MinIO 等)。 | | `access_key` | str \| None | None | 优先使用配置文件/IAM 角色。 | | `secret_key` | str \| None | None | | | `session_token` | str \| None | None | | | `verify` | bool \| str | False | TLS 验证(或 CA 证书包路径)。 | | `base_path` | str | `""` | 此查看器的对象键前缀范围。 | | `use_cache` | bool | False | 文件系统 JSON 缓存。 | | `cache_dir` | str \| None | None | 当 `use_cache=True` 时必需。 | | `ttl` | int (秒) | 300 | 缓存生存时间。 | | `timezone` | str \| None | None | 用于“修改时间”显示的 IANA 时区,例如 `Asia/Seoul`。如果为 None,则显示 boto3 的原始时间戳字符串。 | | `role_arn` | str \| None | None | 如果设置,包装器将在基础凭证之上运行 STS `AssumeRole`,并使用返回的临时密钥(跨账户、多租户)。 | | `role_session_name` | str \| None | `"flask-s3-viewer"` | 已承担会话的标识符。 | | `external_id` | str \| None | None | 转发给 STS 用于需要此 ID 的跨账户角色。 | | `duration_seconds` | int \| None | None | 已承担凭证的有效期(秒)(15 分钟 – 12 小时)。| | `mfa_serial` | str \| None | None | 用于 STS `AssumeRole` 的 MFA 设备 ARN/序列号。| | `token_code` | str \| None | None | 一次性 MFA 代码(与 `mfa_serial` 配对)。 | | `token_code_callback` | callable | None | `token_code` 的替代方案——调用一次以提示用户输入。| 构造函数选项: | 选项 | 说明 | |----------------------|----------------------------------------------------------------| | `app` | Flask 应用实例(可选;稍后通过 `init_app(app)` 传递)。 | | `namespace` | 每个应用唯一。将成为 URL 前缀。 | | `object_hostname` | 外部链接前缀(例如 CloudFront)。 | | `allowed_extensions` | `set[str] \| None` — 只允许这些扩展名的文件上传。 | | `upload_type` | `"default"`(多部分表单上传)或 `"presign"`(预签名)。 | | `title` | 标题 + 浏览器标签页标题文本。默认为 `"Flask S3 Viewer"`。 | | `logo_url` | 自定义标志图像的 URL(绝对路径、`url_for(...)` 或 `/static/...`)。| | `logo_path` | 标志图像的本地文件系统路径——自动内联为 `data:` URI。优先于 `logo_url`。| | `logo_link_url` | (v1.3+) 覆盖标题栏标志 + 标题锚点的点击目标。设置后,用标准导航替换默认的 HTMX 列表重置。| | `template_folder` | 其 Jinja 文件覆盖预置模板的目录(Flask `ChoiceLoader` 模式)。通过 CLI 脚手架生成。| ## AWS 身份验证 `flask-s3-viewer` 遵循 boto3 的默认凭证链,因此以下方式开箱即用: - 静态密钥(`access_key` / `secret_key` / `session_token`) - 命名配置文件(`profile_name='my-profile'`)——包括在 `~/.aws/config` 中设置了 `role_arn` + `source_profile` 的配置文件(boto3 自动处理 AssumeRole) - `AWS_*` 环境变量 - EC2 IMDS / ECS 任务角色 / AWS SSO 缓存 / EKS IRSA(Web Identity OIDC)——当没有其他设置时自动获取。 ``` FlaskS3Viewer( app, namespace="cross-account", config={ "bucket_name": "target-bucket", "region_name": "us-east-1", # Base credentials come from the default chain (profile/env/IRSA). "role_arn": "arn:aws:iam::123456789012:role/AppRole", "external_id": "shared-secret", # optional "role_session_name": "my-app", # default: "flask-s3-viewer" "duration_seconds": 3600, # 15 min – 12 h }, ) ``` 对于受 MFA 保护的角色,提供一个令牌(或用于交互式提示的回调): ``` FlaskS3Viewer( app, namespace="mfa-account", config={ "bucket_name": "secure-bucket", "region_name": "us-east-1", "role_arn": "arn:aws:iam::123456789012:role/AdminRole", "mfa_serial": "arn:aws:iam::123456789012:mfa/alice", "token_code_callback": lambda: input("MFA code: ").strip(), }, ) ``` ## 认证与权限 `flask-s3-viewer` 附带两个可选层。**在没有任何认证连接的情况下,该包的行为与以前完全相同**——两者默认设置为“允许所有人”。 ### 第 1 层:钩子框架(无额外依赖) 使用两个可调用对象插入您现有的登录系统: ``` from flask_s3_viewer.auth import ACTION_LIST, ACTION_UPLOAD, ACTION_DELETE def who_is_asking(request): """Return the user's email (or any opaque id) — None means anonymous.""" return request.headers.get("X-Forwarded-Email") def can_they(email, action, namespace, key): """Authorize a single action. action is one of the ACTION_* constants.""" if action == ACTION_DELETE: return email.endswith("@admin.example.com") return True FlaskS3Viewer( app, namespace="bucket", auth_callback=who_is_asking, permission_callback=can_they, config={...}, ) ``` 五个操作常量是 `ACTION_LIST`、`ACTION_DOWNLOAD`、`ACTION_UPLOAD`、`ACTION_DELETE`、`ACTION_PRESIGN`。 ### RBAC 存储桶切换器 对于多存储桶应用,在 `permission_callback` 中进行硬编码授权,并使用 `visible_namespaces_callback(email, registry)` 来控制哪些存储桶出现在 标题栏切换器中: ``` RBAC = { "alice@example.com": {"assets", "private"}, "bob@example.com": {"assets"}, } def visible_buckets(email, registry): return RBAC.get(email, set()) def can_they(email, action, namespace, key): return namespace in RBAC.get(email, set()) viewer = FlaskS3Viewer( app, namespace="assets", title="Assets", auth_callback=who_is_asking, permission_callback=can_they, visible_namespaces_callback=visible_buckets, config={...}, ) viewer.add_new_one( namespace="private", title="Private", config={...}, ) ``` 切换器仅从 UI 中隐藏无法访问的命名空间。直接的 URL 访问 仍然由 `permission_callback` 检查,因此 RBAC 保持在服务器端。 ### 第 2 层:内置 Google OAuth(可选的 `[auth]` 附加组件) ``` pip install "flask_s3_viewer[auth]" ``` ``` app.secret_key = "..." # required — signs the session cookie FlaskS3Viewer( app, namespace="bucket", google_client_id="...apps.googleusercontent.com", google_client_secret="...", allowed_emails=["alice@example.com"], allowed_domains=["example.com"], config={...}, ) ``` 将 `/auth/login`、`/auth/callback`、`/auth/logout` 安装为应用级路由(位于 FlaskS3Viewer 命名空间前缀之外)。在 Google Cloud Console 中将重定向 URI 配置为 `https:///auth/callback`——即使挂载了多个命名空间,每个应用也只需一个 URI。匿名浏览器访问受保护页面时会自动重定向进行 Google 登录。 混合使用:即使启用了 Google,也可以传递您自己的 `auth_callback` / `permission_callback`,或者在非 Google 部署中使用 `email_allowlist()` 作为权限构建器。 ## 安全 - **路径遍历加固** — 每个用户提供的 `prefix` 都经过验证。令牌 `..`、`.`、空段和 `\` 将被拒绝并返回 HTTP 400。 - **深度防御** — 缓存层额外执行 `realpath` 包容性检查,阻止任何解析到 `cache_dir` 之外的路径。 - **子资源完整性** — 预置的 `htmx.min.js` 引用了其 `sha384` 哈希值;Tailwind 输出是预构建的,并由该包签名。 - **凭证** — 绝不记录凭证。优先使用命名配置文件或实例角色,而非硬编码密钥。 ## 开发 前端资源已预构建并提交到仓库中。编辑模板后重新构建: ``` cd frontend npm install npm run build # writes flask_s3_viewer/blueprints/static/css/app.css ``` CI 验证 CSS 是最新的(`git diff --exit-code`)。 测试: ``` pip install -e ".[dev]" ruff check flask_s3_viewer/ tests/ mypy flask_s3_viewer/ pytest tests/ --cov=flask_s3_viewer ``` ## 从 0.x 版本迁移 请参阅 [`MIGRATION.md`](MIGRATION.md) 获取完整指南。主要变更: - 移除 `s3viewer.register()` — 构造函数现在会自动注册。 - `FlaskS3Viewer.get_instance(ns)` → `FlaskS3Viewer.get_instance(app, ns)`(`get_boto_client`、`get_boto_session` 同理)。 - 重复的命名空间注册现在会引发 `ValueError`,而不是静默重用。 - 未知的命名空间现在返回 HTTP 404,而不是 500。 - 单一模板命名空间 — `template_namespace="base"|"mdl"` 已被忽略并带有弃用警告。 - CLI `--template` 选项已移除。 - `prefix` 中的路径遍历令牌现在返回 HTTP 400。 - 需要 Flask 3.0+ 和 boto3 1.34+。 ## 许可证 [MIT](LICENSE) © Hoiwoong Jung
标签:AWS S3, Flask扩展, Flask框架, HTMX, OAuth认证, Python开发, REST API, S3集成, Tailwind CSS, 云存储管理, 云服务, 前端组件, 后端开发, 多存储桶, 安全性, 搜索功能, 文件上传工具, 文件处理, 文件浏览器, 无构建流程, 现代界面, 用户认证, 缓存管理, 路径安全, 逆向工具, 预签名URL