요약
Discourse 그룹의 **소유자(owner)**인 비관리자(non-admin) 사용자는 웹 UI의 초대 대화상을 통해 해당 그룹이 할당된 상태로 새 멤버를 성공적으로 초대할 수 있습니다. 그러나 REST API(/invites.json, Global-scope API 키, 올바른 Api-Username)를 통해 동일한 작업을 수행하면 403 invalid_access가 반환됩니다 — 단, 페이로드에 group_names/group_ids가 포함된 경우에만 발생합니다. 이는 group_names가 일반 문자열(예: "group_names": "my-group")로 전달되든, 배열(예: "group_names": ["my-group"])로 전달되든 관계없이 발생하며, 두 형식 모두 동일한 403 오류를 유발합니다.
중요한 점은, 모더레이터(Moderator) 권한을 부여해도 API 호출이 해결되지 않으며, 완전한 관리자(Admin) 권한을 부여해야만 해결된다는 것입니다. 이는 모더레이터 권한이 웹 UI, 그룹 미지정 API 초대 등 테스트된 모든 다른 시나리오에서는 충분한 권한임에도 불구하고 그러합니다.
환경
- Discourse 버전: [입력 필요 —
Admin → Upgrade페이지] - API 키 유형: Global scope,
Api-Username= 테스트 사용자의 정확한 사용자 이름
재현 단계
- 비관리자, 비모더레이터 사용자(
<userid>)를 생성하고, 그룹<group_name>의 소유자로 지정합니다. - 웹 UI에서
<userid>로 로그인: 그룹 없이 초대 → 성공.<group_name>이 할당된 상태로 초대 → 성공. - API에서
<userid>로 (Global-scope 키,Api-Username=<userid>):group_names없이POST /invites.json→ 성공.group_names="<group_name>"(문자열 형식)로POST /invites.json→ 403invalid_access.group_names=["<group_name>"](배열 형식)로POST /invites.json→ 403invalid_access(동일한 실패, 두 형식 모두 테스트됨).
<userid>를 모더레이터로 승격. 단계 3의 그룹 지정 API 호출(두 형식 모두) 반복 → 여전히 403invalid_access.<userid>를 관리자로 승격. 단계 3의 그룹 지정 API 호출, 동일한 키, 동일한 페이로드로 반복 → 성공.
기대되는 동작
그룹 소유권만으로도 비스태프(non-staff) 사용자가 웹 UI를 통해 초대 시 해당 그룹을 할당하는 데 충분한 권한이므로, Api-Username을 통해 동일한 사용자로 인증된 API도 동일한 권한을 존중해야 합니다. 최소한, 스태프인 모더레이터가 여기에서 비특권 사용자와 동일하게 동작해서는 안 됩니다. 모더레이터가 역할 없음(no-role)과 동일하게 실패하고, 관리자만 성공한다는 사실은 API 코드 경로가 그룹 소유권이나 일반 스태프 상태(user.staff?, 모더레이터의 경우 true)가 아닌 user.admin?을 구체적으로 확인하고 있음을 시사합니다.
실제 동작
API는 실행 중인 사용자가 완전한 관리자가 아닌 한, 모든 그룹 지정 초대(문자열 또는 배열 파라미터 형식)에 대해 403을 반환합니다. 그룹 소유권(웹 UI를 통해 작동 확인)과 모더레이터/스태프 상태(직접 승격 테스트를 통해 확인)는 모두 API 경로에는 충분하지 않습니다. 이는 웹 UI 또는 그룹 미지정 API 케이스에서는 충분함에도 불구하고 그러합니다.
Discourse 팀에 대한 질문
API 인증을 통한 그룹 지정 초대 코드 경로는 웹 UI / 세션 인증 경로가 그룹 소유권이나 user.staff?을 확인하는 방식과 달리 user.admin?을 명시적으로 요구합니까? 그렇다면 이는 의도된 것(문서화되지 않은 API 전용 제한)입니까, 아니면 이 작업에 대해 API 가디언 체크가 웹 가디언 체크와 다른 버그입니까?
요청 시 전체 요청/응답 로그(키 비공개 처리)를 제공할 수 있습니다.