Skip to content

[BE][RBAC] MVP2-0 Issue 0-4: team membership과 user direct permission 검증 기준 변경 #77

Description

@yoonki1207

목적

MVP2-0 Organization Membership / Invitation Foundation의 핵심 백엔드 전환 작업이다. 기존에는 특정 user에게 direct workflow/LLM permission을 부여하려면 그 user가 target organization의 어떤 team에든 먼저 속해 있어야 했다. 이 기준을 organization_memberships.membership_state='active'인 organization member 기준으로 바꾼다.

변경 후 정책은 다음과 같다.

  • active organization member만 team에 추가할 수 있다.
  • active organization member만 workflow/LLM user direct permission grant 대상이 될 수 있다.
  • team membership은 더 이상 user direct permission의 전제 조건이 아니다.
  • organization member가 remove되면 해당 organization의 team membership과 user direct permission row를 hard delete로 cleanup한다.
  • 기존 MVP1 team/resource permission API request/response shape는 유지한다.

문서 정합성

기준 문서: docs/implementation-plan/mvp-2-0-organization-membership-plan.md

이 이슈는 문서의 다음 항목과 직접 대응한다.

  • Issue 0-4. [BE][RBAC] team membership과 user direct permission 검증 기준 변경
    • team add 시 active organization membership 확인
    • user direct workflow/LLM permission grant 시 organization membership 확인
    • _ensure_grantee_user_membership 제거 또는 의미 변경
    • member removal cleanup 구현
  • 7.2 상태 정의
    • active만 resource 접근 가능성, team 추가 가능, direct permission 대상 가능
    • invited, suspended, removed는 모두 resource 접근/team 추가/direct permission 대상 불가
    • removed 시 team membership과 user direct permission은 hard delete
  • 8.1 team_memberships
    • migration 이후 모든 team membership row는 active organization membership row를 전제로 한다.
    • 신규 team membership 생성 시 active organization membership이 없으면 400을 반환한다.
  • 8.2 user_*_permissions
    • 모든 user direct permission은 active organization membership을 전제로 한다.
    • team membership은 더 이상 user direct permission의 전제 조건이 아니다.
    • organization에서 user가 제거되면 해당 organization의 user direct permission을 revoke한다.
  • 12.3 Member removal cleanup
    • organization member removal은 team membership delete, user_* permission delete, organization membership removed update를 하나의 transaction에서 처리한다.
  • 12.4 permission grant/revoke
    • team grant는 기존대로 team 기준
    • user direct grant는 active organization member 기준
  • 13.7 Remove member, 13.8 상태 전이표, 13.9 MVP 1 API 호환성
    • remove는 cleanup 포함
    • suspend는 cleanup하지 않고 helper가 fail-closed 처리
    • 기존 MVP1 endpoint는 유지하고 내부 검증 기준만 변경
  • 22. 완료 기준
    • Team, User direct permission, Cleanup 항목과 직접 연결된다.

현재 코드 기준 변경 후보

현재 구현 전 상태에 가까운 지점:

  • apps/gateway/services/team_service.py
    • _ensure_grantee_user_membership()TeamMembership join으로 user direct permission grant 대상을 검증한다.
    • TeamService.add_membership()가 target user의 active organization membership을 확인하지 않고 team membership row를 생성한다.
    • TeamService.grant_resource_permission()grantee_type='user'일 때 _ensure_grantee_user_membership()을 호출한다.
    • TeamService.revoke_resource_permission()는 기존 shape를 유지해야 하며, cleanup/manual revoke를 위해 target user가 active가 아니어도 revoke 가능해야 한다.
  • apps/shared/services/permissions.py
    • Issue 0-2에서 추가되는 has_active_organization_membership, get_organization_auth_state를 이 이슈의 service validation에서 재사용해야 한다.
    • 이 이슈에서 permission 계산 로직을 새로 중복 구현하지 않는다.

선행 조건 / 의존성

Blocked by:

  • MBA-53: Issue 0-3 organization member/invitation API

Transitive dependency:

  • MBA-51: Issue 0-1 organization_memberships DB/model/migration
  • MBA-52: Issue 0-2 organization membership 기반 permission helper 전환

Blocks:

  • MBA-54: Issue 0-5 Organization Members UI
  • MBA-55: Issue 0-6 regression 및 acceptance smoke

구현 범위

1. active organization membership 검증 helper 사용

목표: Gateway service layer에서 target user가 target organization의 active member인지 일관되게 검사한다.

작업:

  • 기존 _ensure_grantee_user_membership()를 제거하거나 의미를 변경한다.
  • 권장 이름: _ensure_grantee_organization_membership() 또는 _ensure_active_organization_member().
  • Issue 0-2에서 만든 shared helper를 사용한다.
    • has_active_organization_membership(db, user_id, organization_id)
    • 또는 get_organization_auth_state(db, user_id, organization_id)
  • 직접 query를 새로 작성해야 한다면 0-2 helper와 policy가 어긋나지 않게 shared helper로 끌어올린다.

검증 기준:

  • target user가 존재해야 한다.
  • target user가 deactivated 상태면 실패한다.
  • membership row가 없어도 실패한다.
  • membership_state='active'가 아니면 실패한다.
  • organization_auth_statemember 또는 manager만 허용한다.
  • target organization mismatch는 실패한다.

권장 실패 응답:

  • status code: 400
  • detail: 기존 client 호환을 고려해 큰 이유 노출 없이 Grantee user is not an active organization member 또는 기존 메시지와 호환되는 문구 사용
  • caller가 manager/manage 권한이 없는 경우는 기존대로 403

2. team add 검증 기준 변경

대상:

  • TeamService.add_membership()
  • POST /api/v1/teams/{team_id}/members
  • legacy alias endpoint가 있으면 동일 적용

작업:

  • team 조회 후 team이 target organization에 속하고 active인지 확인한다.
  • current user가 organization manager인지 확인하는 기존 정책은 유지한다.
  • target user가 active organization member인지 확인한 뒤 team membership을 생성한다.
  • active organization membership이 없으면 team membership을 생성하지 않는다.
  • 기존 team membership row가 이미 있더라도 target user가 더 이상 active organization member가 아니라면 성공으로 반환하지 않는다.
  • team membership 생성이 organization membership을 암묵적으로 생성하면 안 된다.

정책:

  • active member: team add 가능
  • invited member: team add 불가
  • suspended member: team add 불가
  • removed member: team add 불가
  • no membership: team add 불가
  • deactivated user: team add 불가

3. team remove semantics 유지

대상:

  • TeamService.remove_membership()
  • DELETE /api/v1/teams/{team_id}/members/{user_id}

작업:

  • team membership 제거는 hard delete 유지
  • organization membership은 제거하지 않는다.
  • organization member lifecycle과 team membership lifecycle을 분리한다.
  • existing endpoint response shape는 유지한다.

검증:

  • team에서 제거해도 organization_memberships row는 그대로 남는다.
  • team에서 제거된 user가 active organization member라면 이후 user direct permission grant 대상이 될 수 있다.

4. user direct workflow permission grant 기준 변경

대상:

  • TeamService.grant_resource_permission()grantee_type='user', resource_type='workflow'
  • /api/v1/permissions/workflows/{workflow_id}/users/{user_id} 형태의 legacy endpoint가 있으면 동일 적용
  • /api/v1/teams/permissions generic endpoint가 있으면 동일 적용

작업:

  • resource organization mismatch 검증은 기존대로 유지한다.
  • current user가 resource manager 또는 organization manager인지 확인하는 기존 정책은 유지한다.
  • grantee user 검증 기준을 team membership에서 active organization membership으로 변경한다.
  • target user가 team에 없어도 active organization member면 grant를 허용한다.
  • target user가 active organization member가 아니면 grant를 차단한다.
  • 기존 UserWorkflowPermission create/update semantics와 unique constraint 사용 방식은 유지한다.

정책:

  • active organization member + no team membership: grant 가능
  • active organization member + team membership 있음: grant 가능
  • invited/suspended/removed/no-membership/deactivated user: grant 불가

5. user direct LLM credential permission grant 기준 변경

대상:

  • TeamService.grant_resource_permission()grantee_type='user', resource_type='llm_credential'
  • /api/v1/permissions/llm-credentials/{credential_id}/users/{user_id} 형태의 legacy endpoint가 있으면 동일 적용

작업:

  • workflow user direct grant와 같은 active organization membership 기준을 적용한다.
  • LLM credential organization mismatch 검증은 기존대로 유지한다.
  • existing request/response shape는 유지한다.
  • 기존 UserLLMPermission create/update semantics와 unique constraint 사용 방식은 유지한다.

정책:

  • active organization member + no team membership: grant 가능
  • invited/suspended/removed/no-membership/deactivated user: grant 불가

6. user direct permission revoke는 active membership을 요구하지 않음

대상:

  • TeamService.revoke_resource_permission()
  • workflow/LLM user revoke endpoint

작업:

  • revoke는 cleanup/manual recovery 목적이 있으므로 grantee가 active organization member인지 검사하지 않는다.
  • caller의 manager/manage 권한 검증과 resource organization mismatch 검증은 유지한다.
  • row가 없을 때 기존 idempotent behavior가 있으면 유지한다.

이유:

  • removed/suspended/deactivated user의 stale direct permission row를 관리자가 revoke할 수 있어야 한다.
  • member removal cleanup 실패나 과거 데이터 잔존을 수동으로 정리할 수 있어야 한다.

7. organization member removal cleanup 구현 또는 정합성 보강

대상:

  • Issue 0-3에서 추가되는 organization member service의 remove method
  • 예상 endpoint: DELETE /api/v1/organizations/{organization_id}/members/{user_id}

작업:

  • remove는 하나의 transaction 안에서 처리한다.
  • 마지막 manager 제거 금지.
  • 자기 자신 제거 금지.
  • affected row count를 계산한다.
    • team_memberships
    • user_workflow_permissions
    • user_llm_permissions
    • MVP2 knowledge permission table이 이미 존재하면 user_knowledge_permissions
  • 해당 organization의 target user team membership을 hard delete한다.
  • 해당 organization의 target user direct permission을 hard delete한다.
  • organization_memberships.membership_stateremoved로 변경한다.
  • removed_at을 기록한다.
  • audit log에 cleanup count metadata를 남긴다.
  • 중간 실패 시 일부 cleanup만 commit되면 안 된다.

주의:

  • suspended 전환 시에는 team/direct permission row를 삭제하지 않는다.
  • reactivate 시에는 suspend 중 유지된 row가 다시 효력을 가진다.
  • removed 후 재초대/accept해도 과거 team/direct permission은 자동 복구되지 않는다.

8. MVP1 API compatibility 유지

유지해야 하는 것:

  • POST /api/v1/teams/{team_id}/members endpoint shape
  • DELETE /api/v1/teams/{team_id}/members/{user_id} endpoint shape
  • workflow team permission grant/revoke shape
  • workflow user permission grant/revoke shape
  • LLM credential team permission grant/revoke shape
  • LLM credential user permission grant/revoke shape
  • permission mutation audit action의 기본 category/action 구조

변경되는 것:

  • team add target validation 기준
  • user direct grant target validation 기준
  • organization member removal cleanup side effect

변경하지 말아야 하는 것:

  • team permission 계산 방식 자체
  • team permission grant/revoke 대상 검증
  • resource auth_state enum
  • team membership table schema
  • 기존 endpoint URL/HTTP method/request body/response model

테스트 범위

이 이슈에서 최소한 service/API regression test를 추가한다. 0-6에서 더 넓은 regression을 수행하되, 0-4의 정책은 여기서 먼저 고정한다.

권장 테스트 위치:

  • apps/gateway/tests/services/test_team_service_permissions.py
  • apps/gateway/tests/api/test_team_memberships.py
  • apps/gateway/tests/api/test_resource_permissions.py
  • organization member removal service/API test는 0-3에서 만든 파일에 추가

필수 테스트:

Team add

  • active organization member를 team에 추가할 수 있다.
  • active organization member가 이미 team member면 기존 idempotent behavior를 유지한다.
  • invited member를 team에 추가할 수 없다.
  • suspended member를 team에 추가할 수 없다.
  • removed member를 team에 추가할 수 없다.
  • no membership user를 team에 추가할 수 없다.
  • deactivated user를 team에 추가할 수 없다.
  • inactive team에는 member를 추가할 수 없다.
  • team organization과 membership organization이 다르면 추가할 수 없다.

Team remove

  • team remove는 team membership만 삭제한다.
  • team remove는 organization membership을 삭제하거나 removed 상태로 바꾸지 않는다.
  • team remove 후에도 active organization member에게 user direct permission을 부여할 수 있다.

Workflow user direct grant

  • team에 속하지 않은 active organization member에게 workflow direct builder/viewer/operator/manager permission을 줄 수 있다.
  • active organization member + 기존 team membership user에게 grant가 기존처럼 동작한다.
  • invited/suspended/removed/no-membership/deactivated user에게 workflow direct permission을 줄 수 없다.
  • resource organization mismatch는 기존대로 실패한다.
  • caller가 manage 권한이 없으면 기존대로 실패한다.

LLM user direct grant

  • team에 속하지 않은 active organization member에게 LLM direct permission을 줄 수 있다.
  • invited/suspended/removed/no-membership/deactivated user에게 LLM direct permission을 줄 수 없다.
  • credential organization mismatch는 기존대로 실패한다.
  • caller가 manage 권한이 없으면 기존대로 실패한다.

Revoke

  • removed/suspended/non-active user의 stale workflow direct permission row도 manager가 revoke할 수 있다.
  • removed/suspended/non-active user의 stale LLM direct permission row도 manager가 revoke할 수 있다.
  • 없는 row revoke는 기존 idempotent behavior를 유지한다.

Organization member remove cleanup

  • active member remove 시 해당 organization의 team memberships가 삭제된다.
  • active member remove 시 해당 organization의 user workflow permissions가 삭제된다.
  • active member remove 시 해당 organization의 user LLM permissions가 삭제된다.
  • 다른 organization의 team/direct permission row는 삭제하지 않는다.
  • invited member remove도 removed 상태로 전환된다.
  • suspended member remove도 cleanup 후 removed 상태가 된다.
  • 마지막 manager 제거는 차단된다.
  • 자기 자신 제거는 차단된다.
  • cleanup count가 response/audit metadata에 반영된다.
  • transaction 실패 시 partial cleanup이 남지 않는다.

Acceptance criteria

  • team에 속하지 않은 active organization member에게 workflow/LLM direct permission을 줄 수 있다.
  • organization member가 아닌 user에게 workflow/LLM direct permission을 줄 수 없다.
  • invited/suspended/removed/no-membership/deactivated user에게 workflow/LLM direct permission을 줄 수 없다.
  • organization member가 아닌 user를 team에 추가할 수 없다.
  • invited/suspended/removed/no-membership/deactivated user를 team에 추가할 수 없다.
  • team membership 생성은 organization membership을 암묵적으로 만들지 않는다.
  • team membership 제거는 organization membership을 제거하지 않는다.
  • organization member 제거 시 해당 organization의 team membership과 user direct permission이 cleanup된다.
  • cleanup은 하나의 transaction으로 처리된다.
  • suspend/reactivate는 team/direct permission row를 삭제하지 않는다.
  • user direct revoke는 target user의 active membership을 요구하지 않는다.
  • 기존 MVP1 permission endpoint URL/request/response shape가 유지된다.
  • 0-2 permission helper와 0-4 service validation 기준이 서로 어긋나지 않는다.

권장 검증 명령

PYTHONPATH=$PWD apps/gateway/.venv/bin/python -m pytest apps/gateway/tests/services/test_team_service_permissions.py
PYTHONPATH=$PWD apps/gateway/.venv/bin/python -m pytest apps/gateway/tests/api/test_team_memberships.py
PYTHONPATH=$PWD apps/gateway/.venv/bin/python -m pytest apps/gateway/tests/api/test_resource_permissions.py
PYTHONPATH=$PWD apps/gateway/.venv/bin/python -m pytest apps/shared/tests/services/test_permissions.py

통합 확인:

./scripts/test.sh

Non-goals

  • organization_memberships 모델/migration/backfill 추가는 0-1 범위다.
  • permission 계산 앞단 fail-closed helper 구현은 0-2 범위다. 이 이슈는 그 helper를 service validation에 재사용한다.
  • organization member/invitation endpoint 전체 구현은 0-3 범위다. 이 이슈는 remove cleanup 정책을 정합성 있게 완성/보강한다.
  • Members UI와 picker filtering은 0-5 범위다.
  • 전체 regression gate와 MVP1 demo smoke는 0-6 범위다.
  • MVP2 knowledge/RAG/data-source permission enforcement 본작업은 이 이슈 범위가 아니다. 단, user_knowledge_permissions table이 이미 존재하는 경우 remove cleanup 대상에 포함할 수 있다.

리스크 / 주의점

  • stale team membership row가 남아 있을 때 add_membership이 성공처럼 반환하면 removed/suspended user가 team permission을 되살릴 수 있다. active organization membership을 먼저 확인한다.
  • direct permission row cleanup이 누락되면 removed user가 재초대 후 과거 권한을 자동 복구받을 수 있다. MVP2-0 정책은 과거 권한 복구 없음이다.
  • suspend와 remove를 혼동하면 reactivate 동작이 문서와 달라진다. suspend는 row 유지, remove는 cleanup이다.
  • revoke까지 active membership을 요구하면 stale permission cleanup이 어려워진다. grant와 revoke 검증 기준을 분리한다.
  • 기존 MVP1 client compatibility를 위해 endpoint shape 변경이나 auth_state enum 변경은 피한다.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions