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.
16 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 |
|---|---|---|---|---|
| アイデンティティプロバイダーからSnapOtterへユーザーとグループを同期するSCIM 2.0プロビジョニングを設定します。Okta、Azure AD / Entra ID、カスタム連携をカバーします。 | 06ee702b386e | human | ed7b44a3f0db | 2 |
SCIMプロビジョニング
SnapOtterは、ユーザーとグループの自動プロビジョニングのためにSCIM 2.0 (System for Cross-domain Identity Management) を実装しています。アイデンティティプロバイダーは、ユーザーアカウントの作成、更新、無効化、再有効化を行い、グループメンバーシップを自動的に同期できます。
::: tip エンタープライズ機能
SCIMプロビジョニングには、scim機能を含むエンタープライズライセンスが必要です。teamプランでは利用できません。この機能がない場合、SCIMエンドポイントは(ディスカバリを除いて)すべて403を返します。
:::
前提条件
- 公開URLで到達可能な、稼働中のSnapOtterインスタンス
scim機能を含むエンタープライズライセンスキー- 完全に有効な権限セットを備えた組み込み SnapOtter
adminアカウント。委任されたカスタム ロールまたは管理者権限が不足している管理者 API キーは、グローバル SCIM トークンを生成または取り消すことができません。 - アイデンティティプロバイダーのプロビジョニング設定への管理者アクセス
クイックスタート
- SCIMベアラートークンを生成します:
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: ベアラートークン(ステップ1のトークンを貼り付ける)
- Base URL:
認証
SCIMエンドポイントは、ユーザーセッションやAPIキーとは別の専用ベアラートークンを使用します。
トークンの生成
POST /api/v1/enterprise/scim/token は新しい SCIM トークンを生成します。トークンはインスタンス全体でユーザーをプロビジョニングおよび変更できるため、このエンドポイントには、完全な有効な管理者権限セットを持つ組み込みの admin ロールが必要です。 users:manage をカスタム ロールで保持するだけでは十分ではありません。
トークンは平文で一度だけ返されます。SnapOtterはscryptハッシュのみを保存します。トークンを紛失した場合は、失効させて新しく生成してください。
同時に有効なSCIMトークンは1つだけです。新しいトークンを生成すると、以前のものが置き換えられます。
::: warning アップグレード後のトークン再発行
従来のバージョン管理されていない SCIM トークンは拒否されます。 so_scim_v2_... トークンを発行するリリースにアップグレードした後、プロビジョニングを再開する前に、新しいトークンを生成し、アイデンティティ プロバイダーを更新します。
:::
トークンの失効
DELETE /api/v1/enterprise/scim/token は、現在の SCIM トークンを取り消します。これには、トークン生成と同じ完全な組み込みの管理要件があります。
レート制限
SCIMエンドポイントは、トークンごとに1分あたり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クエリパラメータをサポートします(1ページあたり最大200件)。
フィルタリングは、次の属性に対してeq (equals) のみをサポートします:
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でページ分割されます(1ページあたり最大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ベアラートークン
- Base URL:
- Test API Credentialsをクリックし、次にSaveします。
- Provisioning > To Appで、次を有効にします:
- Create Users
- Update User Attributes
- Deactivate Users
- Push Groupsで、どのOktaグループをSnapOtterチームとして同期するかを設定します。
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ベアラートークン
- Tenant URL:
- Test Connectionをクリックし、次にSaveします。
- Mappingsで、ユーザーおよびグループの属性マッピングを設定します。通常はデフォルトで動作しますが、
userNameが希望どおりuserPrincipalNameまたはmailにマッピングされていることを確認してください。 - Provisioning StatusをOnに設定して保存します。
Azureは固定の同期サイクル(通常40分ごと)でユーザーとグループをプロビジョニングします。
ディスカバリエンドポイント
次の3つのエンドポイントは認証なしで利用でき、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:
MAX_USERS環境変数の制限は、SCIMによるユーザー作成では適用されません。ユーザー数に上限を設ける必要がある場合は、IdP側で割り当てを管理してください。 - 1トークン: 同時に有効なSCIMトークンは1つだけです。複数の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が1分あたり1000件を超えるリクエストを送信しています。これは通常、大規模な初回同期中に発生します。ほとんどのIdPは、レート制限のウィンドウがリセットされた後に自動的に再試行します。問題が続く場合は、IdPのプロビジョニング同期間隔を確認してください。
ユーザーがデプロビジョニングされたがUIから削除されない
SCIMのDELETEはソフト無効化です。無効化されたユーザーは、無効ステータスで管理者のユーザー一覧に引き続き表示されます。これはデータを保持するための仕様です。ロールはdisabled:<original-role>として表示されます。