Skip to content

[BE][RBAC] MVP2-0 Issue 0-2: organization membership 기반 permission helper 전환 #73

Description

@yoonki1207

목적

MVP 2-0의 organization_memberships DB foundation이 들어간 뒤, shared permission helper가 active organization membership을 resource permission 계산의 선행 조건으로 사용하게 전환한다.

핵심 목표는 기존 team_memberships 기반 permission 계산을 그대로 유지하되, 그 앞단에 organization_memberships.membership_state='active' 확인을 추가하는 것이다. active organization member가 아니면 team permission row나 user direct permission row가 존재해도 workflow/LLM credential 접근은 fail-closed 되어야 한다.

선행 조건

Blocked by:

  • MBA-51: [DB][RBAC] MVP2-0 Issue 0-1: organization_memberships 모델과 migration 추가

이 이슈는 OrganizationMembership 모델과 아래 constant가 존재한다는 전제로 구현한다.

ORGANIZATION_MEMBERSHIP_INVITED = "invited"
ORGANIZATION_MEMBERSHIP_ACTIVE = "active"
ORGANIZATION_MEMBERSHIP_SUSPENDED = "suspended"
ORGANIZATION_MEMBERSHIP_REMOVED = "removed"

ORGANIZATION_AUTH_MEMBER = "member"
ORGANIZATION_AUTH_MANAGER = "manager"

문서 기준 및 정합성 확인

Source of truth:

  • docs/implementation-plan/mvp-2-0-organization-membership-plan.md
  • docs/data-model/rbac-permission-policy.md
  • docs/data-model/physical-data-model.md

정합성 확인:

  • 계획서 Issue 0-2의 작업 범위는 permission helper 전환이다.
  • 계획서 11.1 신규 helper의 helper 4개를 추가/전환한다.
  • 계획서 11.2 resource permission 계산 전제에 맞춰 workflow/LLM credential permission 계산 앞단에서 active organization membership을 확인한다.
  • 계획서 11.3 fail-closed 정책에 맞춰 invited/suspended/removed/no membership user는 resource permission row가 있어도 거부한다.
  • organization.created_by/organization.managed_by legacy fallback은 owner/manager 전용 예외로 유지한다.
  • 기존 team permission 계산 join 방식과 user direct permission additive allow 정책은 유지한다.
  • 이 이슈는 organization member/invitation API, team add 검증 기준 변경, user direct permission grant 대상 검증 변경, member removal cleanup, FE UI를 구현하지 않는다. 해당 작업은 Issue 0-3/0-4/0-5 범위다.

현재 코드 기준

현재 주요 변경 지점:

  • apps/shared/services/permissions.py
    • _manager_state_for_organization()은 현재 organization.created_by/managed_by만 manager로 본다.
    • has_organization_manager_permission()은 membership row를 보지 않는다.
    • get_effective_workflow_auth_state()는 active organization membership 선검증 없이 team/direct permission row를 읽는다.
    • get_effective_llm_credential_auth_state()도 active organization membership 선검증 없이 team/direct permission row를 읽는다.
  • apps/shared/tests/services/test_permissions.py
    • 현재 fake DB 기반 permission helper unit test가 있다.
    • legacy owner fallback, team/direct permission precedence, fail-closed 기본 동작 테스트가 이미 있으므로 MVP2-0 membership state 테스트를 추가한다.

구현 범위

1. 신규 helper 추가

파일:

  • apps/shared/services/permissions.py

추가 helper:

get_organization_membership(db, user_id, organization_id)
has_active_organization_membership(db, user_id, organization_id)
get_organization_auth_state(db, user_id, organization_id)
has_organization_manager_permission(db, user_id, organization_id)

권장 반환/동작:

get_organization_membership(db, user_id, organization_id)

  • user_id, organization_idcoerce_uuid()로 변환한다.
  • 변환 실패 시 None을 반환한다.
  • Organization이 존재하고 is_active=True인지 확인한다.
  • active organization이 아니면 None을 반환한다.
  • OrganizationMembership에서 (organization_id, user_id) row를 조회한다.
  • permission helper에서는 relationship lazy loading에 의존하지 않고 명시 query를 사용한다.

has_active_organization_membership(db, user_id, organization_id)

  • get_organization_membership() 결과의 membership_state == 'active'일 때만 True를 반환한다.
  • invited, suspended, removed, missing row는 모두 False다.
  • deactivated user 차단이 가능한 구조라면 User.deactivated_at is None도 함께 확인한다. 어렵다면 별도 helper 또는 TODO가 아니라 테스트 가능한 쿼리로 처리한다.

get_organization_auth_state(db, user_id, organization_id)

반환 후보:

  • "manager"
  • "member"
  • "none"

판정 순서:

  1. UUID coercion 실패 시 "none" 반환
  2. organization이 없거나 inactive면 "none" 반환
  3. active OrganizationMembership row가 있으면 organization_auth_state를 반환
  4. active membership이 없더라도 user가 organization.created_by 또는 organization.managed_by이면 legacy fallback으로 "manager" 반환
  5. 그 외에는 "none" 반환

주의:

  • organization_auth_statemember/manager는 resource auth state의 viewer/operator/builder/manager와 의미가 다르다.
  • 하지만 organization manager는 resource scope에서 기존 정책처럼 resource manager override를 받는다.

has_organization_manager_permission(db, user_id, organization_id)

  • 기존 함수명은 유지한다.
  • 내부 구현을 get_organization_auth_state(...) == ORGANIZATION_AUTH_MANAGER 기준으로 전환한다.
  • legacy fallback은 get_organization_auth_state() 내부에서만 제한적으로 처리한다.

2. Workflow permission helper 전환

파일:

  • apps/shared/services/permissions.py

대상 함수:

  • get_effective_workflow_auth_state()
  • has_workflow_permission()은 위 함수를 그대로 통해 동작해야 한다.

새 판정 순서:

  1. user_id, workflow_id, organization_id scope를 기존처럼 coerce/resolve한다.
  2. workflow organization과 requested organization이 불일치하면 none을 반환한다.
  3. organization이 없거나 inactive면 none을 반환한다.
  4. get_organization_auth_state(db, user_uuid, organization_uuid)를 호출한다.
  5. 결과가 manager면 resource manager를 즉시 반환한다.
  6. 결과가 member가 아니면 none을 반환한다.
  7. 기존 team workflow permission query를 수행한다.
  8. 기존 user workflow direct permission query를 수행한다.
  9. 기존 stronger_resource_auth_state 우선순위로 가장 강한 resource auth state를 반환한다.

중요:

  • active membership이 없는 일반 user는 team permission row가 있어도 none이다.
  • active membership이 없는 일반 user는 user direct permission row가 있어도 none이다.
  • organization creator/managed_by legacy fallback은 active membership이 없어도 manager로 허용한다.
  • team permission 계산 query 자체는 기존 방식과 동일해야 한다.

3. LLM credential permission helper 전환

파일:

  • apps/shared/services/permissions.py

대상 함수:

  • get_effective_llm_credential_auth_state()
  • has_llm_credential_permission()은 위 함수를 그대로 통해 동작해야 한다.

새 판정 순서:

  1. user_id, llm_credential_id, organization_id scope를 기존처럼 coerce/resolve한다.
  2. credential organization과 requested organization이 불일치하면 none을 반환한다.
  3. organization-scoped credential이면 organization이 active인지 확인한다.
  4. get_organization_auth_state(db, user_uuid, organization_uuid)를 호출한다.
  5. 결과가 manager면 resource manager를 즉시 반환한다.
  6. 결과가 member가 아니면 none을 반환한다.
  7. 기존 team LLM permission query를 수행한다.
  8. 기존 user LLM direct permission query를 수행한다.
  9. 기존 stronger_resource_auth_state 우선순위로 가장 강한 resource auth state를 반환한다.

기존 credential owner fallback 주의:

  • 기존 코드에는 organization scope가 없는 credential에서 credential.user_id == user_id이면 manager를 반환하는 fallback이 있다.
  • MVP2-0 문서의 핵심은 organization-scoped resource에 대한 membership 선검증이다.
  • legacy user-scoped credential fallback을 유지할지 제거할지는 기존 MVP1 회귀 위험을 보고 판단하되, organization_id가 있는 credential에는 반드시 organization membership 선검증을 적용한다.
  • 판단이 애매하면 테스트로 기존 user-scoped fallback 유지와 organization-scoped fail-closed를 분리한다.

4. Fail-closed 정책 구현

다음 조건은 workflow/LLM credential permission에서 none 또는 False가 되어야 한다.

  • organization이 inactive
  • active organization membership이 없음
  • membership_state가 invited
  • membership_state가 suspended
  • membership_state가 removed
  • resource organization과 requested organization이 불일치
  • user가 deactivated

예외:

  • organization.created_by 또는 organization.managed_by인 legacy owner/manager는 active membership이 누락되어도 manager fallback을 허용한다.
  • 이 예외는 owner/manager 전용이다. 일반 user의 team/direct permission row에는 적용하지 않는다.

5. 테스트 추가/수정

파일 후보:

  • apps/shared/tests/services/test_permissions.py
  • 필요하면 helper 전용 테스트 파일 분리 가능

필수 테스트:

  1. active organization member + team workflow viewer -> workflow viewer
  2. no organization membership + team workflow viewer row만 존재 -> workflow none
  3. no organization membership + user direct workflow builder row만 존재 -> workflow none
  4. invited membership + user direct workflow builder -> workflow none
  5. suspended membership + team workflow manager -> workflow none
  6. removed membership + team workflow manager -> workflow none
  7. active organization member + user direct workflow builder + no team -> workflow builder
  8. active manager membership -> workflow manager
  9. organization.created_by legacy fallback + no membership -> workflow manager
  10. organization.managed_by legacy fallback + no membership -> workflow manager
  11. inactive organization -> workflow none even if membership/permission rows exist
  12. requested organization mismatch -> workflow none
  13. active organization member + team LLM operator/use -> LLM operator/use allowed
  14. no organization membership + user direct LLM manager row -> LLM none
  15. invited/suspended/removed membership + LLM permission row -> LLM none
  16. active manager membership -> LLM manager
  17. deactivated user -> workflow/LLM none

기존 테스트 유지:

  • legacy auth state normalization
  • workflow action matrix
  • LLM action matrix
  • team/direct permission precedence
  • SQLAlchemy row unpacking
  • audit-only auth state fail-closed for resource permissions

Fake DB 테스트를 유지할 경우:

  • query 순서가 바뀌므로 FakeDb.first_values/all_values를 새 helper 호출 순서에 맞게 수정한다.
  • 또는 SQLAlchemy in-memory/session fixture를 쓰는 테스트로 일부 helper 테스트를 대체한다.
  • 테스트가 helper 구현의 query call order에 과도하게 묶이지 않도록, membership helper 단위 테스트와 effective auth state 테스트를 분리하는 것을 권장한다.

완료 조건

  • get_organization_membership()이 추가된다.
  • has_active_organization_membership()이 추가된다.
  • get_organization_auth_state()가 추가된다.
  • has_organization_manager_permission()이 membership 기준 + legacy fallback 기준으로 전환된다.
  • workflow effective auth state 계산이 active organization membership을 선검증한다.
  • LLM credential effective auth state 계산이 organization-scoped credential에 대해 active organization membership을 선검증한다.
  • active organization manager membership은 resource manager override를 받는다.
  • organization.created_by/organization.managed_by legacy fallback은 유지된다.
  • active member가 아니면 team permission row가 있어도 접근이 차단된다.
  • active member가 아니면 user direct permission row가 있어도 접근이 차단된다.
  • team permission 계산 방식은 기존 join/우선순위를 유지한다.
  • user direct permission은 기존 additive allow 우선순위를 유지한다.
  • invited/suspended/removed membership 상태는 fail-closed 된다.
  • inactive organization, resource organization mismatch, deactivated user는 fail-closed 된다.
  • permission helper unit test가 추가/수정되고 통과한다.
  • 기존 MVP1 permission helper 테스트가 의도하지 않게 삭제되지 않는다.

명시적 제외 범위

이 이슈에서 하지 않는다.

  • organization_memberships 모델/migration 추가: MBA-51 범위
  • get_user_primary_organization_id()를 organization membership 기준으로 바꾸는 Gateway context 전환
  • ensure_user_default_organization() runtime reconciliation guard
  • organization member/invitation service/API
  • team add 시 active organization membership 검증
  • user direct permission grant 시 active organization membership 검증
  • organization member removal cleanup
  • FE Members UI, invite modal, picker filtering
  • MVP2 user_knowledge_permissions migration 및 knowledge base use enforcement

후속 연결

이 이슈가 끝나면 다음 작업이 가능해진다.

  • Issue 0-3: organization member/invitation API 추가
  • Issue 0-4: team membership과 user direct permission 검증 기준 변경
  • MVP2 본작업의 knowledge/RAG permission enforcement 설계 고정

특히 MVP2의 user_knowledge_permissions, knowledge base use enforcement, RAG node execution check는 organization membership 선검증 정책이 이 이슈에서 고정된 뒤 진행한다.

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