forked from baron/baron-sso
관계 조회 권한 사용자 검색 안내 강화
This commit is contained in:
@@ -695,13 +695,21 @@ func (h *DevHandler) SearchUsers(c *fiber.Ctx) error {
|
||||
if profile == nil {
|
||||
return errorJSON(c, fiber.StatusUnauthorized, "unauthorized: authentication required")
|
||||
}
|
||||
if !isDevConsoleRoleAllowed(normalizeUserRole(profile.Role)) {
|
||||
|
||||
// Tightened Security: Only SuperAdmin bypasses the client-specific manage check.
|
||||
// Regular users (RoleUser) or RPAdmins must have the 'manage' permit for the requested clientId.
|
||||
if normalizeUserRole(profile.Role) != domain.RoleSuperAdmin {
|
||||
clientID := strings.TrimSpace(c.Query("clientId"))
|
||||
if clientID == "" {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "clientId is required for user search")
|
||||
}
|
||||
summary, err := h.loadClientSummary(c.Context(), clientID)
|
||||
if clientID == "" || err != nil || !h.canManageClientRelations(c, profile, summary) {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden")
|
||||
if err != nil || !h.canManageClientRelations(c, profile, summary) {
|
||||
// canManageClientRelations checks for 'manage' permit in Keto.
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: manage permission required for user search")
|
||||
}
|
||||
}
|
||||
|
||||
if h.KratosAdmin == nil {
|
||||
return errorJSON(c, fiber.StatusServiceUnavailable, "kratos admin unavailable")
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
removeClientRelation,
|
||||
} from "../../lib/devApi";
|
||||
import { t } from "../../lib/i18n";
|
||||
import { resolveProfileRole } from "../../lib/role";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
import { ClientDetailTabs } from "./ClientDetailTabs";
|
||||
|
||||
const relationOptions = [
|
||||
@@ -68,6 +70,7 @@ function formatUserLabel(user: DevAssignableUser) {
|
||||
|
||||
function ClientRelationsPage() {
|
||||
const params = useParams();
|
||||
const auth = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
const clientId = params.id ?? "";
|
||||
const [selectedRelations, setSelectedRelations] = useState<RelationOption[]>(
|
||||
@@ -79,6 +82,11 @@ function ClientRelationsPage() {
|
||||
null,
|
||||
);
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
|
||||
const systemRole = resolveProfileRole(
|
||||
auth.user?.profile as Record<string, unknown> | undefined,
|
||||
);
|
||||
|
||||
const { data: clientData } = useQuery({
|
||||
queryKey: ["client", clientId],
|
||||
queryFn: () => fetchClient(clientId),
|
||||
@@ -95,6 +103,19 @@ function ClientRelationsPage() {
|
||||
enabled: clientId.length > 0,
|
||||
});
|
||||
|
||||
// Calculate permissions for UI hints and button states
|
||||
const isSuperAdmin = systemRole === "super_admin";
|
||||
const myUserId = auth.user?.profile.sub;
|
||||
const isRpAdmin = useMemo(() => {
|
||||
if (isSuperAdmin) return true;
|
||||
if (!relationData?.items || !myUserId) return false;
|
||||
return relationData.items.some(
|
||||
(item) => item.subject === `User:${myUserId}` && item.relation === "admins"
|
||||
);
|
||||
}, [relationData?.items, myUserId, isSuperAdmin]);
|
||||
|
||||
const canManageRelations = isRpAdmin || isSuperAdmin;
|
||||
|
||||
const isRelationshipViewForbidden =
|
||||
(error as AxiosError | null)?.response?.status === 403;
|
||||
const relationshipViewForbiddenMessage = t(
|
||||
@@ -221,6 +242,16 @@ function ClientRelationsPage() {
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!canManageRelations) {
|
||||
toast(
|
||||
t(
|
||||
"msg.dev.clients.relationships.add_forbidden_viewer",
|
||||
"'관계 조회' 권한만으로는 새로운 관계를 추가하거나 사용자를 검색할 수 없습니다. 'RP 관리자' 권한이 필요합니다.",
|
||||
),
|
||||
"error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!selectedUser) {
|
||||
toast(
|
||||
t(
|
||||
@@ -398,18 +429,26 @@ function ClientRelationsPage() {
|
||||
)}
|
||||
</div>
|
||||
) : isUserSearchForbidden ? (
|
||||
<div className="px-3 py-2 text-sm text-destructive font-medium">
|
||||
{t(
|
||||
"msg.dev.clients.relationships.search_forbidden_user",
|
||||
"일반 사용자는 관계 추가를 위한 사용자 검색을 사용할 수 없습니다.",
|
||||
)}
|
||||
<div className="px-4 py-8 text-center text-sm text-destructive font-medium border-b border-border/40 bg-destructive/5 flex flex-col gap-2">
|
||||
<p>
|
||||
{t(
|
||||
"msg.dev.clients.relationships.search_forbidden_user",
|
||||
"일반 사용자는 관계 추가를 위한 사용자 검색을 사용할 수 없습니다.",
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground/80 font-normal">
|
||||
{t(
|
||||
"msg.dev.clients.relationships.search_forbidden_user_hint",
|
||||
"'관계 조회' 권한만으로는 사용자 검색이 제한됩니다. 'RP 관리자' 관계가 필요합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
) : (userSearchData?.items ?? []).length > 0 ? (
|
||||
(userSearchData?.items ?? []).map((user) => (
|
||||
<button
|
||||
key={user.id}
|
||||
type="button"
|
||||
className="flex w-full flex-col gap-1 px-3 py-2 text-left hover:bg-muted/40"
|
||||
className="flex w-full flex-col gap-1 px-3 py-2 text-left hover:bg-muted/40 border-b border-border/40 last:border-b-0"
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
handleSelectUser(user);
|
||||
@@ -425,7 +464,7 @@ function ClientRelationsPage() {
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="px-3 py-2 text-sm text-muted-foreground">
|
||||
<div className="px-3 py-4 text-center text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.clients.relationships.search_empty",
|
||||
"검색 결과가 없습니다.",
|
||||
@@ -503,7 +542,7 @@ function ClientRelationsPage() {
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
onClick={handleAdd}
|
||||
disabled={addMutation.isPending}
|
||||
disabled={addMutation.isPending || !canManageRelations}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
@@ -625,7 +664,7 @@ function ClientRelationsPage() {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="gap-2 text-destructive hover:text-destructive"
|
||||
disabled={removeMutation.isPending}
|
||||
disabled={removeMutation.isPending || !canManageRelations}
|
||||
onClick={() =>
|
||||
handleRemove(item.relation, item.subject)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user