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.
24 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 เพื่อซิงก์ผู้ใช้และกลุ่มจากผู้ให้บริการข้อมูลประจำตัว (identity provider) ของคุณไปยัง SnapOtter ครอบคลุม Okta, Azure AD / Entra ID และการผสานรวมแบบกำหนดเอง | 06ee702b386e | human | e8ab3b07c519 | 2 |
การจัดสรรด้วย SCIM
SnapOtter รองรับ SCIM 2.0 (System for Cross-domain Identity Management) สำหรับการจัดสรรผู้ใช้และกลุ่มแบบอัตโนมัติ ผู้ให้บริการข้อมูลประจำตัวของคุณสามารถสร้าง อัปเดต ปิดใช้งาน และเปิดใช้งานบัญชีผู้ใช้ใหม่ รวมถึงซิงก์การเป็นสมาชิกกลุ่มได้โดยอัตโนมัติ
::: tip คุณสมบัติสำหรับองค์กร (Enterprise)
การจัดสรรด้วย SCIM ต้องใช้ไลเซนส์ enterprise ที่มีคุณสมบัติ scim ไม่มีให้ใช้งานในแผน team หากไม่มีคุณสมบัตินี้ ปลายทาง SCIM ทั้งหมด (ยกเว้น discovery) จะคืนค่า 403
:::
ข้อกำหนดเบื้องต้น
- อินสแตนซ์ SnapOtter ที่ทำงานอยู่และเข้าถึงได้ผ่าน URL สาธารณะ
- คีย์ไลเซนส์ enterprise ที่มีคุณสมบัติ
scim - บัญชี SnapOtter
adminในตัวพร้อมชุดสิทธิ์ที่มีผลเต็มรูปแบบ บทบาทที่กำหนดเองที่ได้รับมอบสิทธิ์หรือคีย์ API ของผู้ดูแลระบบที่ขาดสิทธิ์ของผู้ดูแลระบบไม่สามารถสร้างหรือเพิกถอนโทเค็น SCIM ส่วนกลางได้ - สิทธิ์ผู้ดูแลระบบในการตั้งค่าการจัดสรรของผู้ให้บริการข้อมูลประจำตัวของคุณ
เริ่มต้นใช้งานอย่างรวดเร็ว
- สร้างโทเคน bearer สำหรับ 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: โทเคน 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 | Team | ได้ | ได้ | ได้ | ได้ |
::: warning
Groups ใน SCIM แมปกับ teams ของ SnapOtter ไม่ใช่บทบาท (roles) SCIM ไม่สามารถตั้งค่าบทบาทของผู้ใช้ได้ ผู้ใช้ทั้งหมดที่สร้างผ่าน SCIM จะได้รับบทบาท user หากต้องการเปลี่ยนบทบาทของผู้ใช้ ให้ใช้ UI ผู้ดูแลระบบของ SnapOtter
:::
การดำเนินการกับผู้ใช้
สร้างผู้ใช้
POST /api/v1/scim/v2/Users
สร้างบัญชีผู้ใช้ใหม่โดยตั้งค่า authProvider เป็น scim และบทบาท user ผู้ใช้จะถูกกำหนดให้อยู่ในทีม Default หาก active เป็น false บทบาทจะถูกตั้งเป็น disabled แทน
แอตทริบิวต์ที่จำเป็น: userName ทางเลือก: externalId, emails, active (ค่าเริ่มต้น true)
แสดงรายการและกรองผู้ใช้
GET /api/v1/scim/v2/Users
คืนค่ารายการผู้ใช้แบบแบ่งหน้า รองรับพารามิเตอร์ query startIndex และ count (สูงสุด 200 ผลลัพธ์ต่อหน้า)
การกรองรองรับเฉพาะ eq (เท่ากับ) เท่านั้น บนแอตทริบิวต์เหล่านี้:
userName eq "jane"externalId eq "ext-12345"
ตัวดำเนินการและแอตทริบิวต์การกรองอื่น ๆ จะคืนค่า HTTP 400
รับข้อมูลผู้ใช้
GET /api/v1/scim/v2/Users/:id
คืนค่าผู้ใช้รายเดียวตาม ID ผู้ใช้ SnapOtter ของพวกเขา
แทนที่ผู้ใช้
PUT /api/v1/scim/v2/Users/:id
แทนที่แอตทริบิวต์ของผู้ใช้ รองรับ userName, externalId, emails และ active การเปลี่ยนชื่อผู้ใช้จะถูกตรวจสอบความขัดแย้ง (409 หากชื่อผู้ใช้ใหม่ถูกใช้โดยผู้ใช้อื่นแล้ว)
แพตช์ผู้ใช้
PATCH /api/v1/scim/v2/Users/:id
อัปเดตบางส่วนโดยใช้ SCIM PatchOp การดำเนินการที่รองรับ:
| การดำเนินการ | Paths |
|---|---|
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 ใด ๆ ได้อีกต่อไป ข้อมูลของพวกเขา (ไฟล์ ประวัติ) จะยังคงถูกเก็บไว้
เปิดใช้งานผู้ใช้อีกครั้ง
หากต้องการเปิดใช้งานผู้ใช้ที่เคยถูกปิดใช้งานไปแล้วอีกครั้ง ให้ส่งคำขอ PUT หรือ PATCH พร้อม active: true 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 }
]
}
:::
การดำเนินการกับกลุ่ม
Groups ใน SCIM แมปกับทีมของ 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
รองรับการดำเนินการเหล่านี้:
| การดำเนินการ | Path | ผลลัพธ์ |
|---|---|---|
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: โทเคน bearer สำหรับ 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: โทเคน bearer สำหรับ SCIM ที่สร้างไว้ข้างต้น
- Tenant URL:
- คลิก Test Connection แล้วคลิก Save
- ภายใต้ Mappings ให้กำหนดค่าการแมปแอตทริบิวต์ของผู้ใช้และกลุ่ม โดยปกติค่าเริ่มต้นจะใช้งานได้ แต่ให้ตรวจสอบว่า
userNameแมปกับuserPrincipalNameหรือmailตามที่ต้องการ - ตั้ง Provisioning Status เป็น On แล้วบันทึก
Azure จัดสรรผู้ใช้และกลุ่มตามรอบการซิงก์ที่กำหนดไว้ (โดยทั่วไปทุก ๆ 40 นาที)
ปลายทางสำหรับ Discovery
ปลายทางทั้งสามนี้ใช้งานได้โดยไม่ต้องยืนยันตัวตน และอธิบายความสามารถของเซิร์ฟเวอร์ SCIM:
| ปลายทาง | คำอธิบาย |
|---|---|
GET /api/v1/scim/v2/ServiceProviderConfig |
ความสามารถของเซิร์ฟเวอร์และคุณสมบัติที่รองรับ |
GET /api/v1/scim/v2/Schemas |
คำนิยาม schema ของ 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) ไม่ได้ถูกนำมาใช้งาน - การดำเนินการแบบ Bulk: ไม่รองรับ
- Sort และ ETag: ไม่รองรับ
- บทบาท: SCIM ไม่สามารถกำหนดบทบาทของ SnapOtter ได้ ผู้ใช้ที่จัดสรรทั้งหมดจะได้รับบทบาท
user - MAX_USERS: ขีดจำกัดของตัวแปรสภาพแวดล้อม
MAX_USERSไม่ได้ถูกบังคับใช้เมื่อสร้างผู้ใช้ผ่าน SCIM หากคุณต้องการจำกัดจำนวนผู้ใช้ ให้จัดการการมอบหมายใน IdP ของคุณ - หนึ่งโทเคน: มีโทเคน SCIM ที่ใช้งานได้เพียงหนึ่งโทเคนในแต่ละครั้ง หาก IdP หลายรายต้องเข้าถึง SCIM พวกเขาต้องใช้โทเคนร่วมกัน
- กลุ่มคือทีม: Groups ใน SCIM สอดคล้องกับทีม ไม่ใช่บทบาทหรือกลุ่มสิทธิ์
การแก้ไขปัญหา
403 "SCIM provisioning requires an enterprise license with the scim feature"
ไลเซนส์ของคุณไม่มีคุณสมบัติ scim หรือไม่มีการกำหนดค่าไลเซนส์ SCIM ต้องใช้ไลเซนส์แผน enterprise ตรวจสอบว่าตั้งค่า 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
DELETE ของ SCIM คือการปิดใช้งานแบบซอฟต์ ผู้ใช้ที่ถูกปิดใช้งานจะยังคงปรากฏในรายการผู้ใช้ของผู้ดูแลระบบพร้อมสถานะปิดใช้งาน นี่เป็นการออกแบบเพื่อให้ข้อมูลของพวกเขาถูกเก็บรักษาไว้ บทบาทของพวกเขาจะแสดงเป็น disabled:<original-role>