Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ V2 内置 ACL、CE 原生 Tag 和已安装 Guard 的 Checkout 不应重新受旧
## 数据库唯一规则:当前五表

- V2 仅保留 `cf_library_storage`、`cf_dir_acl`、`cf_sso_group_map`、`cf_edit_guard`、`cf_audit_event`。新装五表一次性创建,已有卷只校验,不自动迁移或修补。
- DEV 旧数据全部放弃。存储绑定从 Seafile 原生库重新发现,ACL/共享与组织角色从 Etech 重同步,Guard/审计从空表开始;旧 `cf_tag`/Resource/Intent/Session/Outbox 等绝不恢复。
- DEV 旧数据全部放弃。存储绑定从 Seafile 原生库重新发现,ACL/共享与组织角色从客户 Provider 重同步,Guard/审计从空表开始;旧 `cf_tag`/Resource/Intent/Session/Outbox 等绝不恢复。
- DEV 如需清表重建,仅允许停写后由人工管理员按当前 DDL 操作,不做产品级迁移/历史追溯/归档库;**绝不删除或改写 Seafile 原生文件版本和存储路由**。

## Agent2 本地编辑 V2 边界
Expand Down
2 changes: 1 addition & 1 deletion BRANCHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
- Docker `build/v2/source-set.json` 固定了一套特定 V2 原生源码/补丁提交;其 pin 可能比最新 `dev` 旧,不能用其 CI 结果代表当前三个 `dev`。
- 现存 `release.yaml`、`build/cloudfile_14.0/` 涉及旧版扩展构建配方,含可能浮动的 `ref: dev`;**在明确审查实际构建入口和锁定三个 SHA 前,不得将其直接当成 V2 当前发布指令**。
- 三仓协同变更要核对权限、身份、存储、文件字节或 Schema 的实际接口;必要时按同一固定 SHA 组合验证,不把不同时间的 `dev` 混成一套发行。
- 真正发布的镜像必须具备可追溯的源码组合与 Digest。仅提交文档、源码合并、构建完成、DEV 部署及 Etech 业务验收是不同的事实。
- 真正发布的镜像必须具备可追溯的源码组合与 Digest。仅提交文档、源码合并、构建完成、DEV 部署及客户业务验收是不同的事实。

## 四、尽量少跑无关构建和测试

Expand Down
28 changes: 14 additions & 14 deletions CLOUDFILE.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ CloudFile 尚未正式发布,只有 **五张当前扩展表**。产品不实
| 表 | 当前数据来源 |
| --- | --- |
| `cf_library_storage` | Seafile CE 原生 `Repo`、`RepoStorageId`(含 RepoTrash/GarbageRepos 状态)重新识别登记 |
| `cf_dir_acl` | Etech 重新下发库目录 ACL、继承和共享规则 |
| `cf_sso_group_map` | Etech 重新同步组织/角色到 CE 原生 Group |
| `cf_dir_acl` | 客户 Provider 重新下发库目录 ACL、继承和共享规则 |
| `cf_sso_group_map` | 客户 Provider 重新同步组织/角色到 CE 原生 Group |
| `cf_edit_guard` | 新增独占编辑 Guard,从空表开始 |
| `cf_audit_event` | 从空表开始记录 CE Activity 未覆盖的 Tag/Checkout/Checkin 业务动作 |

Expand All @@ -18,11 +18,11 @@ CloudFile 尚未正式发布,只有 **五张当前扩展表**。产品不实

## 开发环境重新初始化(仅 DEV,人工执行一次)

1. 停止 CloudFile 文件写入、Hub 和 Etech 同步任务;确认 Agent2 没有正在进行的 Checkout/未完成写入。
1. 停止 CloudFile 文件写入、Hub 和 客户 Provider 同步任务;确认 Agent2 没有正在进行的 Checkout/未完成写入。
2. 由 DEV 数据库管理员清理旧 CloudFile 扩展表并按**当前五表 DDL**重新建立空表。仅处理 CloudFile 扩展表,不删除 CE 的 `Repo`、`RepoStorageId`、`Branch`、`Commit` 或原生 Tag;不读取旧扩展表数据,不生成归档库或迁移台账。
3. 运行现有管理员接口 `POST /api/v2.1/admin/cloudfile/storage/reconcile/`,请求体 `{"repo_ids":[]}`,从实际 Seafile 原生库重新登记 `cf_library_storage`。核对未登记/受阻库,不强行重绑存储、不移动文件。
4. 由 Etech 重新执行目录权限/共享全量下发,以及组织、角色和用户组关系同步。CloudFile `POST /api/v2.1/admin/cloudfile/directory/entities/sync/` 只核对实体与原生组,不代表用户成员关系已经完成;成员关系仍由现有 Etech/SSO 同步链路提供。
5. `cf_edit_guard`、`cf_audit_event` 不回填;开始新测试后产生新锁和新审计事件。验证普通浏览/上传、目录 ACL、Etech 账号/组、本地编辑三模式。
4. 由 客户 Provider 重新执行目录权限/共享全量下发,以及组织、角色和用户组关系同步。CloudFile `POST /api/v2.1/admin/cloudfile/directory/entities/sync/` 只核对实体与原生组,不代表用户成员关系已经完成;成员关系仍由现有 客户 Provider/SSO 同步链路提供。
5. `cf_edit_guard`、`cf_audit_event` 不回填;开始新测试后产生新锁和新审计事件。验证普通浏览/上传、目录 ACL、客户 Directory 账号/组、本地编辑三模式。

不做增量迁移,不保留旧版本 API/SchemaRunner/Job/Outbox。任何特殊历史恢复均属于产品以外的临时工作,不能进入 CloudFile 日常安装与 CI。

Expand Down
15 changes: 15 additions & 0 deletions deploy/compose/.env.enterprise-sso.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Enterprise Directory overrides; merge into the deployment environment before release.
# Generic OAuth defaults stay provider-neutral. No credentials belong in this file.
CF_SSO_OAUTH_LOGIN_ID_CLAIM=userId
# Namespace used by directory ACL rules, cf_sso_group_map rows and identity keys.
# Keep the value stable for an existing deployment; consumers may choose their own.
CF_ACL_PROVIDER=external
# Audience of the machine-authenticated Directory JWT (must match the Directory side).
CF_DIRECTORY_AUDIENCE=cloudfile-directory
# Optional: enable ONLY after the trusted Directory /administrators machine endpoint
# and CE admin ownership have been verified. Uses existing Directory URL/key/secret.
# A failed one-time fetch warns without preventing the CloudFile service startup.
CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false
# Optional: require one known business administrator account in the signed roster.
# This is a health assertion only; it never grants privileges by username alone.
CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=
13 changes: 0 additions & 13 deletions deploy/compose/.env.etech-sso.example

This file was deleted.

2 changes: 1 addition & 1 deletion deploy/compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ CF_SSO_OAUTH_PROVIDER=
# provider; change them for one that does not.
CF_SSO_OAUTH_UID_CLAIM=sub
# EAP OAuth requires Authentik to supply a verified string userId in tokens
# and userinfo. Etech MUST override CF_SSO_OAUTH_LOGIN_ID_CLAIM=userId.
# and userinfo. Enterprise deployments MUST override CF_SSO_OAUTH_LOGIN_ID_CLAIM=userId.
# Keep CF_SSO_EAP_PROFILE_IDENTITY=false; the historical bridge is retired.
# EAP prepares users through trusted Directory before the existing OAuth flow.
CF_SSO_OAUTH_LOGIN_ID_CLAIM=login_id
Expand Down
2 changes: 1 addition & 1 deletion deploy/v0.2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ docker compose -f deploy/v0.2/compose.ingress.yml up -d
历史验证入口(无需继续执行):`python3 tests/verify.py changed --plan`,
`python3 tests/ingress_runtime.py --image cloudfile/cloudfile:14.0.8-v0.2-rc-app`。
探针运行实际只读 Nginx 与校验证书的 HTTPS,使用透明响应夹具核对路由/方法/
请求体/头/Range。它不冒充新的 CE 身份、文件权限或真实 eTech 联验。
请求体/头/Range。它不冒充新的 CE 身份、文件权限或真实客户联验。
2 changes: 1 addition & 1 deletion docs/acl-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- 当前 Hub 在主体轨道上依次优先 `user`、`org`、`role`,同类型适用规则以更深层覆盖浅层;同类型同层多匹配,显式拒绝优先,否则 `rw` 高于 `r`。**这是当前实现,不应误写为尚未落地的“跨主体路径优先”。**
- `inherit=false` 仅阻止该规则向子级传播,不切断其自身目录所继承的其他有效规则。
- 原生库 `r` 不是目录显式 `rw` 的永久上限;CE 硬只读、禁用库或失效身份仍不得被目录规则绕开。
- Etech 组织树默认不因为 parent 关系自动包含子组织;主体成员应依据可信 Directory 投影。
- 企业 Directory 组织树默认不因为 parent 关系自动包含子组织;主体成员应依据可信 Directory 投影。

## 执行与失效

Expand Down
2 changes: 1 addition & 1 deletion docs/permission-adjudication-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
CloudFile 不存在托管库这一概念。每个库均保留 Seafile CE 原生 RepoOwner/SharedRepo/RepoGroup 的库资格,以及同一 `cf_dir_acl` 存储的目录 ACL 和共享目录资格。目录规则是路径级事实,不能使整个库进入另一权限模式。

- 库级资格由 CE 原生权限和可信身份计算,正常情况下 `r/rw`;目录规则默认继承,可在有效作用域内缩权或扩权。
- Hub 的 `cloudfile_ext/v2/acl_runtime.py:Authority.effective()` 结合 `acl_policy.py` 负责 user / org / role、目录祖先、共享根的统一判定。Etech 的组织父子层级本身不自动扩大成员身份。
- Hub 的 `cloudfile_ext/v2/acl_runtime.py:Authority.effective()` 结合 `acl_policy.py` 负责 user / org / role、目录祖先、共享根的统一判定。企业 Directory 的组织父子层级本身不自动扩大成员身份。
- 只有没有 CE 库资格的用户取得明确的正向 `share_mode=true` 目录规则时,才可从对应共享目录根进入;不得因此遍历库根、父目录和兄弟目录。
- `invisible` 表示不可访问且不展示。不存在可持久化的 `none` 目录规则。管理身份与内容 `rw` 分离。
- C/Go 负责不可绕过的真实字节、对象、CAS/Branch Commit 终判;调用者身份来自可信会话/票据,不能信任 Commit 作者或客户端提供的主体。
Expand Down
2 changes: 1 addition & 1 deletion docs/upstream-patches/cloudfile-hub.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# OAuth UID 修复(2026-10-20):保留上游回调入口;仅明确的业务 UID 映射走严格绑定。
# Etech Directory 配置缺失正确映射或 UserInfo 缺 UID 时拒绝,不进入 CE 兼容建号;
# 企业 Directory 配置缺失正确映射或 UserInfo 缺 UID 时拒绝,不进入 CE 兼容建号;
# 适配器位于独立 V2 包,避免精简源码产物导入未携带的旧扩展包。
# cloudfile-hub 允许修改的上游文件。
#
Expand Down
6 changes: 3 additions & 3 deletions scripts/scripts_cf14.0/cloudfile_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def endpoint(name, required=True):
'use trusted Directory identity preparation')
lines.append('CF_SSO_EAP_PROFILE_IDENTITY = False')
if login_id_claim == 'login_id':
logwarning('For Etech set CF_SSO_OAUTH_LOGIN_ID_CLAIM=userId; '
logwarning('For a trusted enterprise deployment set CF_SSO_OAUTH_LOGIN_ID_CLAIM=userId; '
'Profile.login_id must contain the business UID')

lines += [
Expand Down Expand Up @@ -394,7 +394,7 @@ def endpoint(name, required=True):
'CF_SERVICE_SSO_DIRECTORY_SERVICE_ID = %r'
% get_conf('CF_SERVICE_SSO_DIRECTORY_SERVICE_ID', 'cloudfile'),
# The V2 ACL authority reads its own connection names
# (cloudfile_ext/v2/etech_directory.py: CF_DIRECTORY_URL / KEY_ID /
# (cloudfile_ext/v2/external_directory.py: CF_DIRECTORY_URL / KEY_ID /
# SECRET / GROUP_OWNER). It must resolve the same subjects as the SSO
# membership sync, so derive them from the values above instead of
# asking operators to keep a second copy in .env. Without them every
Expand Down Expand Up @@ -1270,7 +1270,7 @@ def write_seafevents_search_config():
def write_seafevents_audit_config():
"""Do not enable Pro-style access/update/permission audit streams.

V2 Etech operation history reads the CE Activity stream (registered
V2 客户 Provider operation history reads the CE Activity stream (registered
independently) and cf_audit_event for low-frequency tag/Checkout changes.
FileAudit and PermAudit are unnecessary for this scope and would amplify
routine downloads and sync traffic. Disable an older generated [Audit]
Expand Down
11 changes: 8 additions & 3 deletions tests/test_admin_startup_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ def test_zero_exit_without_applied_receipt_is_failure_not_false_success(self):
runner=Mock(return_value=SimpleNamespace(
returncode=0, stdout=stdout))), 'FAILED')

def test_etech_default_admin_compatibility_remains_opt_in(self):
def test_enterprise_default_admin_compatibility_remains_opt_in(self):
root = Path(__file__).resolve().parents[1]
env = (root / 'deploy/compose/.env.etech-sso.example').read_text()
self.assertIn('CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=admin', env)
env = (root / 'deploy/compose/.env.enterprise-sso.example').read_text()
self.assertIn('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false', env)
# The example must not hardcode a customer administrator account: the
# expected-account assertion is an operator opt-in in their own config.
self.assertIn('CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=', env)
self.assertNotIn('CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=admin', env)
self.assertIn('CF_ACL_PROVIDER=external', env)
self.assertIn('CF_DIRECTORY_AUDIENCE=cloudfile-directory', env)

def test_nonroot_command_is_one_safely_quoted_shell_command(self):
spawn = Mock(return_value=SimpleNamespace(returncode=0, stdout='CF_ADMIN_SYNC APPLIED added=1 removed=0 unchanged=0'))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cloudfile_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def test_permission_runtime_middleware_is_installed(self):
def test_authentik_redirect_defaults_to_the_deployment_protocol(self):
# Same default as bootstrap.get_proto(): an unset SEAFILE_SERVER_PROTOCOL
# means HTTP. Generating an HTTPS redirect for an HTTP entry is what made
# the Hub mark sessionid/csrftoken Secure on Etech's HTTP deployment.
# the Hub mark sessionid/csrftoken Secure on 客户 Provider's HTTP deployment.
# The public entry's scheme is declared by the deployment, so an HTTP
# redirect does not additionally require CLOUDFILE_AUTHENTIK_ALLOW_INSECURE
# (that flag stays on the IdP-facing URLs).
Expand Down
6 changes: 3 additions & 3 deletions tests/v2/native_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ def create_account(username):
issuer='https://idp.example.test/', create_account=create_account)
username = identity.bind(subject='oidc-subject', uid='uid-runtime', login_name='E001')
assert identity.bind(subject='oidc-subject', uid='uid-runtime', login_name='E999') == username
catalog = GroupCatalog(connection, account_schema=names['account'], provider='etech',
catalog = GroupCatalog(connection, account_schema=names['account'], provider='external',
create_group=lambda name: ccnet_api.create_group(name, owner))
group = catalog.ensure('role', 'role', 'r1', 'Runtime role')
subject = dict(userId='uid-runtime', status='active', attributes={'login_name': 'E999'},
organizations=[], organization_ancestors=[], roles=[dict(namespace='role', external_id='r1')],
etag='runtime', generated_at=datetime.now(timezone.utc).isoformat())
client = redis.Redis(host=os.environ['REDIS_HOST'])
projector = Memberships(connection, client, identity, account_schema=names['account'], provider='etech', fetch=lambda uid: subject)
projector = Memberships(connection, client, identity, account_schema=names['account'], provider='external', fetch=lambda uid: subject)
cache_key = projector._key('uid-runtime', username)
projector.ensure('uid-runtime', username)
automatic = os.environ.get('CF_V2_AUTO_STORAGE') == '1'
Expand Down Expand Up @@ -205,7 +205,7 @@ def create_account(username):
'subject_type,provider,namespace,external_id,'
'permission,inherit,share_mode,revision) '
'VALUES (%s,UNHEX(SHA2(%s,256)),%s,'
"'user','etech','','uid-runtime','r',1,0,%s)",
"'user','external','','uid-runtime','r',1,0,%s)",
(repo, '/project', '/project', str(uuid4())))
try:
assert requests.get(url, timeout=30).status_code == 403, (
Expand Down
2 changes: 1 addition & 1 deletion tools/gates/test-bootstrap-settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ def test_eap_identity():
CF_SSO_OAUTH_LOGIN_ID_CLAIM='userId')
values = evaluate(load('_settings_block_sso', env)())
check('V2 bridge stays disabled', values['CF_SSO_EAP_PROFILE_IDENTITY'] is False)
check('Etech maps business UID', values['OAUTH_ATTRIBUTE_MAP']['userId'] == (False, 'login_id'))
check('enterprise Directory maps business UID', values['OAUTH_ATTRIBUTE_MAP']['userId'] == (False, 'login_id'))
try:
load('_settings_block_sso', dict(env, CF_SSO_EAP_PROFILE_IDENTITY='true'))()
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion tools/ops/automatic-local-storage/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ FROM ${CLOUDFILE_BASE}
# storage consumers and the corresponding Python routing surface.
COPY payload /tmp/automatic-local-storage
RUN python3 /tmp/automatic-local-storage/apply.py && rm -rf /tmp/automatic-local-storage
LABEL com.sec.etech.automatic-storage="key-root-v1"
LABEL com.sec.cloudfile.automatic-storage="key-root-v1"
Loading
Loading