A release-readiness QA pass over the whole product. The commits split into defects a user would hit and gates that were reporting green while measuring nothing. ## Fixes that change behaviour Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so request.ip came from a client-set header and a forged X-Forwarded-For got past the login limiter. The default is now a private-network trust list. A transient Postgres outage stranded in-flight jobs, leaving finished output on disk with no row pointing at it. A reconciler now resolves those rows and adopts the bytes rather than dropping the work. A Redis connection that moved to a new address wedged every read-blocked consumer, so completions stopped signalling while health still answered 200. Socket timeouts plus subscriber pings recover it. Installing more than one AI bundle left the shared venv multi-versioned and silently broke three tools. The installer now reconciles distributions to one version each. Converting an image to JXL at quality 1 through 4 returned a 500, because libjxl 0.7 rejects the distance those values compute. The quality is floored at what the encoder honours. A missing ffmpeg was also reported to the user as a corrupt upload; it now says the engine is unavailable. RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at 0.22.2, and the release scan was split so it can fail on an unfixed critical instead of hiding it behind ignore-unfixed. ## Gates that could not fail Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs build; coverage discarded its whole report on any failing test; the lint gate skipped root tests, scripts, and two workspaces; and several generated matrices counted a host missing ffmpeg as a passing tool. Each now measures what it claims. Full evidence and the outstanding release items are tracked locally and are not part of this branch.
12 KiB
description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
| description | i18n_source_hash | i18n_provenance | i18n_output_hash | i18n_hash_version |
|---|---|---|---|---|
| 設定 SCIM 2.0 佈建,將使用者與群組從您的身分提供者同步至 SnapOtter。涵蓋 Okta、Azure AD / Entra ID 以及自訂整合。 | 06ee702b386e | human | da12b859e973 | 2 |
SCIM 佈建
SnapOtter 實作 SCIM 2.0(System for Cross-domain Identity Management)以進行自動化的使用者與群組佈建。您的身分提供者可以自動建立、更新、停用及重新啟用使用者帳號,並同步群組成員資格。
::: tip 企業版功能
SCIM 佈建需要具備 scim 功能的 enterprise 授權。team 方案無法使用。若未具備此功能,所有 SCIM 端點(探索端點除外)都會回傳 403。
:::
先決條件
- 一個可透過公開網址存取的執行中 SnapOtter 執行個體
- 具備
scim功能的企業版授權金鑰 - 內建 SnapOtter
admin帳戶及其完整的有效權限集。委託的自訂角色或缺少任何管理權限的管理 API 金鑰無法產生或撤銷全域 SCIM 令牌。 - 您身分提供者佈建設定的管理員存取權
快速開始
- 產生一個 SCIM bearer 權杖:
curl -X POST https://photos.example.com/api/v1/enterprise/scim/token \
-H "Cookie: snapotter-session=YOUR_SESSION" \
-H "Content-Type: application/json"
回應中包含該權杖。請立即儲存;它無法再次取得。
{
"token": "so_scim_v2_a1b2c3d4e5f6...",
"message": "Save this token - it cannot be retrieved again"
}
- 在您的身分提供者中,以下列項目設定 SCIM 佈建:
- Base URL:
https://photos.example.com/api/v1/scim/v2 - Authentication:Bearer 權杖(貼上步驟 1 中的權杖)
- Base URL:
驗證
SCIM 端點使用專屬的 Bearer 權杖,與使用者工作階段及 API 金鑰分開。
產生權杖
POST /api/v1/enterprise/scim/token 產生新的 SCIM 代幣。由於令牌可以跨實例配置和變更用戶,因此此端點需要具有完整有效管理權限集的內建 admin 角色。將 users:manage 保留在自訂角色中是不夠的。
該權杖以明文回傳,且僅回傳一次。SnapOtter 只儲存 scrypt 雜湊值。若您遺失權杖,請撤銷它並產生新的權杖。
同一時間只有一個 SCIM 權杖處於使用中狀態。產生新權杖會取代先前的權杖。
::: warning 升級後重新發行令牌
舊版未版本控制的 SCIM 令牌將被拒絕。升級到頒發 so_scim_v2_... 令牌的版本後,請產生新令牌並更新您的身分提供者,然後再恢復配置。
:::
撤銷權杖
DELETE /api/v1/enterprise/scim/token 撤銷目前的 SCIM 令牌。它具有與令牌生成相同的完整內建管理要求。
速率限制
SCIM 端點的速率限制為每個權杖每分鐘 1000 個請求。超過此限制會回傳 HTTP 429。
支援的資源
| SCIM 資源 | SnapOtter 概念 | 建立 | 讀取 | 更新 | 刪除 |
|---|---|---|---|---|---|
| User | 使用者帳號 | 是 | 是 | 是 | 軟刪除 |
| Group | 團隊 | 是 | 是 | 是 | 是 |
::: warning
SCIM Group 對應到 SnapOtter 的團隊,而非角色。SCIM 無法設定使用者的角色。所有透過 SCIM 建立的使用者都會被指派 user 角色。若要變更使用者的角色,請使用 SnapOtter 管理員 UI。
:::
使用者操作
建立使用者
POST /api/v1/scim/v2/Users
建立一個新的使用者帳號,其 authProvider 設為 scim 並具備 user 角色。該使用者會被指派到 Default 團隊。若 active 為 false,則角色會改設為 disabled。
必要屬性:userName。選用屬性:externalId、emails、active(預設為 true)。
列出並篩選使用者
GET /api/v1/scim/v2/Users
回傳使用者的分頁清單。支援 startIndex 與 count 查詢參數(每頁最多 200 筆結果)。
篩選僅支援 eq(等於),適用於下列屬性:
userName eq "jane"externalId eq "ext-12345"
其他篩選運算子與屬性會回傳 HTTP 400。
取得使用者
GET /api/v1/scim/v2/Users/:id
依 SnapOtter 使用者 ID 回傳單一使用者。
取代使用者
PUT /api/v1/scim/v2/Users/:id
取代使用者的屬性。支援 userName、externalId、emails 與 active。使用者名稱變更會檢查衝突(若新使用者名稱已被其他使用者佔用則回傳 409)。
修補使用者
PATCH /api/v1/scim/v2/Users/:id
使用 SCIM PatchOp 進行部分更新。支援的操作:
| 操作 | 路徑 |
|---|---|
replace |
active、userName、externalId、emails、emails[type eq "work"].value、name.formatted、displayName |
add |
與 replace 相同 |
remove |
externalId、emails |
name.formatted 與 displayName 路徑為了相容性而被接受,但沒有持久效果(SnapOtter 不會另外儲存顯示名稱)。
無值的 replace 操作(其值為一個不含 path 的物件)同樣受支援,鍵為 userName、externalId、emails 與 active。
停用使用者(軟刪除)
DELETE /api/v1/scim/v2/Users/:id
SnapOtter 不會透過 SCIM 硬刪除使用者。相反地,DELETE 會執行軟停用:
- 使用者的角色會從其目前值(例如
editor)變更為disabled:editor,並保留原本的角色。 - 使用者的密碼會被清除。
- 所有使用中的工作階段都會被撤銷。
- 所有 API 金鑰都會被撤銷。
該使用者將無法再登入或使用任何 API 金鑰。他們的資料(檔案、歷史記錄)會被保留。
重新啟用使用者
若要重新啟用先前已停用的使用者,請以 active: true 傳送 PUT 或 PATCH 請求。SnapOtter 會還原停用前的原始角色(例如 disabled:editor 會再次變回 editor)。若無法判定原始角色,則會回退至 user。
::: details 範例:透過 PATCH 停用及重新啟用
// Deactivate
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}
// Reactivate
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": true }
]
}
:::
群組操作
SCIM Group 對應到 SnapOtter 團隊。建立群組會建立一個團隊。群組成員資格控制使用者所屬的團隊。
建立群組
POST /api/v1/scim/v2/Groups
必要:displayName。選用:members({ value: userId } 的陣列)。
列出並篩選群組
GET /api/v1/scim/v2/Groups
篩選僅支援 displayName eq "..."。以 startIndex 與 count 分頁(每頁最多 200 筆結果)。
取得群組
GET /api/v1/scim/v2/Groups/:id
取代群組
PUT /api/v1/scim/v2/Groups/:id
取代群組名稱與完整成員清單。不在新清單中的現有成員會被移至 Default 團隊。
修補群組
PATCH /api/v1/scim/v2/Groups/:id
支援下列操作:
| 操作 | 路徑 | 效果 |
|---|---|---|
add |
members |
將使用者加入團隊 |
remove |
members[value eq "userId"] |
將使用者移至 Default 團隊 |
replace |
displayName |
重新命名團隊 |
replace |
members |
取代所有成員(被移除的成員會移至 Default 團隊) |
刪除群組
DELETE /api/v1/scim/v2/Groups/:id
刪除該團隊。被刪除團隊的所有成員都會被移至 Default 團隊。使用者不會被停用或刪除。
IdP 設定
Okta
- 在 Okta 管理員主控台中,開啟您的 SnapOtter 應用程式(或建立一個)。
- 前往 Provisioning 分頁並點選 Configure API Integration。
- 勾選 Enable API Integration 並輸入:
- Base URL:
https://photos.example.com/api/v1/scim/v2 - API Token:上方產生的 SCIM bearer 權杖
- Base URL:
- 點選 Test API Credentials,然後點選 Save。
- 在 Provisioning > To App 下,啟用:
- Create Users
- Update User Attributes
- Deactivate Users
- 在 Push Groups 下,設定要以 SnapOtter 團隊同步的 Okta 群組。
Azure AD / Entra ID
- 在 Azure 入口網站中,前往您的 SnapOtter 企業應用程式。
- 前往 Provisioning 並將 Provisioning Mode 設為 Automatic。
- 在 Admin Credentials 下,輸入:
- Tenant URL:
https://photos.example.com/api/v1/scim/v2 - Secret Token:上方產生的 SCIM bearer 權杖
- Tenant URL:
- 點選 Test Connection,然後點選 Save。
- 在 Mappings 下,設定使用者與群組屬性對應。預設值通常可正常運作,但請確認
userName依需求對應到userPrincipalName或mail。 - 將 Provisioning Status 設為 On 並儲存。
Azure 會依固定的同步週期(通常每 40 分鐘)佈建使用者與群組。
探索端點
下列三個端點無需驗證即可使用,並描述 SCIM 伺服器的功能:
| 端點 | 說明 |
|---|---|
GET /api/v1/scim/v2/ServiceProviderConfig |
伺服器功能與支援的特性 |
GET /api/v1/scim/v2/Schemas |
User 與 Group 結構定義 |
GET /api/v1/scim/v2/ResourceTypes |
可用的資源類型(User、Group) |
ServiceProviderConfig 會宣告下列功能:
| 特性 | 是否支援 |
|---|---|
| Patch | 是 |
| Bulk | 否 |
| Filter | 是(最多 200 筆結果,僅 eq 運算子) |
| Change password | 否 |
| Sort | 否 |
| ETag | 否 |
限制
- 篩選:僅支援
eq運算子。複雜篩選、and/or運算子、co(contains)與sw(starts with)皆未實作。 - 批次操作:不支援。
- Sort 與 ETag:不支援。
- 角色:SCIM 無法指派 SnapOtter 角色。所有佈建的使用者都會取得
user角色。 - MAX_USERS:SCIM 建立使用者時不會強制執行
MAX_USERS環境變數限制。若您需要限制使用者數量,請在您的 IdP 中管理指派。 - 單一權杖:同一時間只能有一個 SCIM 權杖處於使用中狀態。若多個 IdP 需要 SCIM 存取權,它們必須共用該權杖。
- 群組即團隊:SCIM Group 對應到團隊,而非角色或權限群組。
疑難排解
403 "SCIM provisioning requires an enterprise license with the scim feature"
您的授權未包含 scim 功能,或未設定任何授權。SCIM 需要企業版方案授權。請確認 SNAPOTTER_LICENSE_KEY 已設定,且該授權包含 scim 功能。
401 "Bearer token required"
SCIM 請求未包含 Authorization: Bearer <token> 標頭。請檢查您 IdP 的佈建設定。
401 "Invalid token"
令牌格式錯誤、使用已停用的未版本化格式或與儲存的雜湊不符。產生目前的 so_scim_v2_... 令牌並在 IdP 的組態設定中更新該令牌。
401 "SCIM not configured"
尚未產生任何 SCIM 權杖。請使用 POST /api/v1/enterprise/scim/token 端點來建立一個。
409 "User already exists" / "userName already taken"
已存在使用相同使用者名稱的使用者。這可能在 IdP 重試失敗的建立操作時發生。請在 SnapOtter 管理面板中檢查是否有重複的使用者名稱。
429 "SCIM rate limit exceeded"
IdP 每分鐘傳送超過 1000 個請求。這通常在大型初始同步期間發生。大多數 IdP 會在速率限制視窗重設後自動重試。若問題持續存在,請檢查您 IdP 的佈建同步間隔。
使用者已解除佈建但未從 UI 移除
SCIM DELETE 是軟停用。已停用的使用者仍會以停用狀態顯示在管理員使用者清單中。這是刻意的設計,以便保留他們的資料。他們的角色會顯示為 disabled:<original-role>。