forked from baron/baron-sso
조직도 M2M조회 추가, 자동로그인 보완
This commit is contained in:
@@ -34,6 +34,229 @@ flowchart TD
|
||||
G --> H[RP never parses or stores raw kratos_identity_id]
|
||||
```
|
||||
|
||||
## OIDC Tenant Claim Contract
|
||||
|
||||
Baron은 기본적으로 대표소속 tenant와 전체 소속 tenant 목록을 식별할 수 있도록 `tenant_id`, `joined_tenants`를 ID token claim에 포함할 수 있습니다. RP가 OIDC scope 또는 client metadata 정책을 통해 `tenant` claim을 요청하면 Baron은 여기에 더해 tenant별 상세 정보를 포함합니다. 이 claim은 RP가 UI 표시, 조직 맥락 선택, RP 내부 권한 매핑을 시작하기 위한 입력이며, 최종 권한 판정은 Baron gateway/Keto check 또는 Baron이 발급한 trusted header를 기준으로 해야 합니다.
|
||||
|
||||
기본 claim 구조는 다음과 같습니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"tenant_id": "01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"joined_tenants": [
|
||||
"01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"01970f0b-3448-7bb8-bdc7-16b6a1d2e661"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`tenant` claim을 요청하면 상세 claim 구조는 다음과 같습니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"tenant_id": "01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"joined_tenants": [
|
||||
"01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"01970f0b-3448-7bb8-bdc7-16b6a1d2e661"
|
||||
],
|
||||
"lead_tenants": ["01970f0a-5c28-74d8-a73a-f6e9e9a7b210"],
|
||||
"tenants": {
|
||||
"01970f0a-5c28-74d8-a73a-f6e9e9a7b210": {
|
||||
"id": "01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"slug": "tech-planning",
|
||||
"name": "기술기획팀",
|
||||
"type": "USER_GROUP",
|
||||
"lead": true,
|
||||
"representative": true,
|
||||
"isPrimary": true,
|
||||
"grade": "책임",
|
||||
"jobTitle": "기술기획",
|
||||
"position": "팀장",
|
||||
"parentTenantId": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"ancestors": [
|
||||
{
|
||||
"id": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"slug": "hanmac",
|
||||
"name": "한맥기술",
|
||||
"type": "COMPANY",
|
||||
"parentTenantId": "01970f07-4f01-7d9a-a71e-b53ad508f345"
|
||||
},
|
||||
{
|
||||
"id": "01970f07-4f01-7d9a-a71e-b53ad508f345",
|
||||
"slug": "hanmac-family",
|
||||
"name": "한맥가족",
|
||||
"type": "COMPANY_GROUP",
|
||||
"parentTenantId": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"01970f0b-3448-7bb8-bdc7-16b6a1d2e661": {
|
||||
"id": "01970f0b-3448-7bb8-bdc7-16b6a1d2e661",
|
||||
"slug": "quality",
|
||||
"name": "품질관리팀",
|
||||
"type": "USER_GROUP",
|
||||
"lead": false,
|
||||
"representative": false,
|
||||
"isPrimary": false,
|
||||
"grade": "선임",
|
||||
"jobTitle": "품질관리",
|
||||
"position": "파트원",
|
||||
"parentTenantId": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"ancestors": [
|
||||
{
|
||||
"id": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"slug": "hanmac",
|
||||
"name": "한맥기술",
|
||||
"type": "COMPANY",
|
||||
"parentTenantId": "01970f07-4f01-7d9a-a71e-b53ad508f345"
|
||||
},
|
||||
{
|
||||
"id": "01970f07-4f01-7d9a-a71e-b53ad508f345",
|
||||
"slug": "hanmac-family",
|
||||
"name": "한맥가족",
|
||||
"type": "COMPANY_GROUP",
|
||||
"parentTenantId": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
필드 의미는 다음과 같습니다.
|
||||
|
||||
- `tenant_id`: 기본 claim입니다. 사용자의 대표소속 tenant UUID입니다. 현재 RP/client context tenant가 없더라도 공백으로 내려가지 않습니다.
|
||||
- `joined_tenants`: 기본 claim입니다. 사용자가 claim 상에서 소속된 모든 tenant UUID 목록입니다. `additionalAppointments`의 모든 한맥가족 subtree tenant를 포함합니다.
|
||||
- `lead_tenants`: `tenant` claim 요청 시 포함됩니다. `lead=true`로 판정된 tenant id 목록입니다.
|
||||
- `tenants`: `tenant` claim 요청 시 포함됩니다. tenant UUID를 key로 하는 tenant별 claim map입니다. 멀티 소속이면 소속 tenant마다 하나씩 포함되며, `slug`는 별도 필드로 내려갑니다.
|
||||
- `tenants.*.lead`: 해당 tenant에서 lead 권한 또는 조직장 역할이 있으면 `true`입니다. Baron 입력에서는 `lead`, `isLead`, `isOwner`, `isManager`를 수용할 수 있습니다.
|
||||
- `tenants.*.representative`: 대표조직이면 `true`입니다. Baron 입력에서는 `representative`, `isPrimary`, `primary`를 수용할 수 있습니다.
|
||||
- `tenants.*.grade`: 직급입니다.
|
||||
- `tenants.*.jobTitle`: 직무입니다.
|
||||
- `tenants.*.position`: 직책입니다.
|
||||
- `tenants.*.parentTenantId`: 현재 tenant의 직속 parent tenant UUID입니다. 최상위 root면 `null`입니다.
|
||||
- `tenants.*.ancestors`: 직속 상위 tenant부터 `hanmac-family` root까지의 parent chain입니다.
|
||||
|
||||
대표소속 결정 정책은 다음과 같습니다.
|
||||
|
||||
- 명시적인 `tenant_id`가 있으면 이를 대표소속으로 사용합니다.
|
||||
- 명시적인 대표소속이 없으면 `additionalAppointments`에서 `representative=true`, `isPrimary=true`, `primary=true`인 소속을 사용합니다.
|
||||
- 대표 표시가 없으면 가장 먼저 등록된 소속 tenant를 대표소속으로 사용합니다.
|
||||
- 생성 시 소속 tenant가 하나도 없으면 Baron이 PERSONAL tenant를 자동 생성하고, 해당 PERSONAL tenant UUID를 `tenant_id`와 `joined_tenants`에 포함합니다.
|
||||
- RP/client의 tenant context는 대표소속을 덮어쓰지 않습니다. RP context tenant가 필요한 경우 별도 필드나 RP route context로 다뤄야 합니다.
|
||||
|
||||
한맥가족(`hanmac-family`) subtree에 속한 tenant claim은 다음 규칙을 따릅니다.
|
||||
|
||||
- `TenantService.GetTenant` 기준 parent chain이 `hanmac-family` root에 도달한 경우에만 한맥가족 확장 필드를 보강합니다.
|
||||
- `additionalAppointments`만 존재하고 tenant별 namespaced traits map이 없어도 `tenant_id` 또는 `additionalAppointments[].tenantId`를 기준으로 `tenants` 항목을 생성할 수 있습니다.
|
||||
- 사용자가 여러 tenant에 소속되면 기본 claim인 `joined_tenants`에는 모든 소속 tenant가 포함됩니다.
|
||||
- `tenant` claim 요청 시 `tenants`에도 모든 소속 tenant의 상세가 포함되고, `lead_tenants`에는 그중 `lead=true`인 tenant만 포함됩니다.
|
||||
- 직급/직무/직책과 대표조직/lead 여부는 사용자 소속 metadata(`additionalAppointments`)를 우선합니다.
|
||||
- `ancestors`는 직속 상위 tenant부터 root 방향으로 정렬되며, root가 `hanmac-family`일 때까지만 포함합니다.
|
||||
- 기본 tenant와 각 ancestor 객체는 `parentTenantId`를 포함합니다. 이 필드로 parent edge를 바로 그릴 수 있습니다.
|
||||
|
||||
주의사항:
|
||||
|
||||
- Tenant tree, 직급, 직무, 직책은 PostgreSQL Business SoT와 tenant/user metadata를 기준으로 합니다. Kratos traits는 인증 식별 정보 중심으로 유지해야 하며, 관계형 데이터의 영구 SoT로 취급하지 않습니다.
|
||||
- Token 크기가 커질 수 있으므로 RP가 긴 조직 전체 정보를 필요로 하면 ID token claim보다 userinfo/profile API 또는 Baron backend API 연동을 우선 검토합니다.
|
||||
- RP는 `lead_tenants` 또는 `tenants.*.lead`만으로 보안상 중요한 권한을 단독 판정하지 않습니다. 권한 변경/민감 리소스 접근은 Keto 기반 Baron authorization contract를 함께 사용해야 합니다.
|
||||
|
||||
### Issue #775 구현 결과 예시
|
||||
|
||||
아래 예시는 #775 구현 후 `tenant_id=01970f0a-5c28-74d8-a73a-f6e9e9a7b210`, 사용자 `additionalAppointments`에 대표 소속 `tech-planning`과 겸직 소속 `quality`가 함께 있고, 두 tenant의 parent chain이 `hanmac -> hanmac-family`로 이어지는 경우 ID token에 내려가는 데이터 형태입니다. 예시의 `id` 값은 UUID 형식의 샘플이며, `slug`와 다릅니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "hanmac-user@example.com",
|
||||
"name": "한맥 사용자",
|
||||
"tenant_id": "01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"joined_tenants": [
|
||||
"01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"01970f0b-3448-7bb8-bdc7-16b6a1d2e661"
|
||||
],
|
||||
"lead_tenants": [
|
||||
"01970f0a-5c28-74d8-a73a-f6e9e9a7b210"
|
||||
],
|
||||
"tenants": {
|
||||
"01970f0a-5c28-74d8-a73a-f6e9e9a7b210": {
|
||||
"id": "01970f0a-5c28-74d8-a73a-f6e9e9a7b210",
|
||||
"slug": "tech-planning",
|
||||
"name": "기술기획팀",
|
||||
"type": "USER_GROUP",
|
||||
"lead": true,
|
||||
"representative": true,
|
||||
"isPrimary": true,
|
||||
"grade": "책임",
|
||||
"jobTitle": "기술기획",
|
||||
"position": "팀장",
|
||||
"parentTenantId": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"ancestors": [
|
||||
{
|
||||
"id": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"slug": "hanmac",
|
||||
"name": "한맥기술",
|
||||
"type": "COMPANY",
|
||||
"parentTenantId": "01970f07-4f01-7d9a-a71e-b53ad508f345"
|
||||
},
|
||||
{
|
||||
"id": "01970f07-4f01-7d9a-a71e-b53ad508f345",
|
||||
"slug": "hanmac-family",
|
||||
"name": "한맥가족",
|
||||
"type": "COMPANY_GROUP",
|
||||
"parentTenantId": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"01970f0b-3448-7bb8-bdc7-16b6a1d2e661": {
|
||||
"id": "01970f0b-3448-7bb8-bdc7-16b6a1d2e661",
|
||||
"slug": "quality",
|
||||
"name": "품질관리팀",
|
||||
"type": "USER_GROUP",
|
||||
"lead": false,
|
||||
"representative": false,
|
||||
"isPrimary": false,
|
||||
"grade": "선임",
|
||||
"jobTitle": "품질관리",
|
||||
"position": "파트원",
|
||||
"parentTenantId": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"ancestors": [
|
||||
{
|
||||
"id": "01970f08-91da-7286-bd19-882fb98d1f2c",
|
||||
"slug": "hanmac",
|
||||
"name": "한맥기술",
|
||||
"type": "COMPANY",
|
||||
"parentTenantId": "01970f07-4f01-7d9a-a71e-b53ad508f345"
|
||||
},
|
||||
{
|
||||
"id": "01970f07-4f01-7d9a-a71e-b53ad508f345",
|
||||
"slug": "hanmac-family",
|
||||
"name": "한맥가족",
|
||||
"type": "COMPANY_GROUP",
|
||||
"parentTenantId": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"emails": [
|
||||
"hanmac-user@example.com"
|
||||
],
|
||||
"names": {
|
||||
"name": "한맥 사용자"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
RP 소비 기준:
|
||||
|
||||
- lead tenant를 빠르게 찾을 때는 `lead_tenants`를 우선 사용합니다.
|
||||
- 전체 소속 tenant 목록은 `joined_tenants`로 읽고, 각 소속의 상세 조직 맥락은 `tenants[joined_tenants[n]]`에서 읽습니다.
|
||||
- 대표소속의 상세 조직 맥락은 `tenants[tenant_id]`에서 읽습니다.
|
||||
- 상위 조직 breadcrumb은 `tenants[tenant_id].ancestors`를 직속 상위부터 root 방향으로 표시합니다.
|
||||
- 조직 트리 edge는 기본 tenant와 각 ancestor의 `parentTenantId`를 사용해 그립니다.
|
||||
- 대표조직 여부는 `representative`를 우선 사용하고, 기존 primary 표현 호환이 필요하면 `isPrimary`를 함께 읽습니다.
|
||||
|
||||
## obj_id 조회 흐름
|
||||
|
||||
`obj_id`는 Keto check의 target object입니다. 명시적으로 전달된 `obj_id`가 있으면 정규화 후 사용하고, 없으면 route context에서 `client_id`, `tenant_id` 순서로 추론합니다. 둘 다 없으면 RP가 명확한 target object를 제공하지 않은 것이므로 요청을 거부해야 합니다.
|
||||
|
||||
Reference in New Issue
Block a user