Don't show global event listeners in the admin UI

Closes #34602

Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
Alexander Schwartz 2024-11-07 14:56:03 +01:00 committed by Michal Hajas
parent de14e1c310
commit f4a208de6d
7 changed files with 195 additions and 6 deletions

View File

@ -2070,7 +2070,7 @@ addClientProfile=Add client profile
maxFailureWaitSeconds=Max wait
userEventsRegistered=User events registered
renameAGroup=Rename group
eventConfigError=Could not save event configuration {{error}}
eventConfigError=Could not save event configuration\: {{error}}
confirmAccessTokenTitle=Regenerate registration access token?
target=Target
impersonateConfirmDialog=Are you sure you want to log in as this user? If this user is in the same realm with you, your current login session will be logged out before you log in as this user.

View File

@ -1,8 +1,19 @@
import { ActionGroup, Button } from "@patternfly/react-core";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { SelectControl, SelectVariant } from "@keycloak/keycloak-ui-shared";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import {
KeycloakSpinner,
useFetch,
SelectControl,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { useState } from "react";
import { fetchAdminUI } from "../../context/auth/admin-ui-endpoint";
import { useAdminClient } from "../../admin-client";
type EventListenerRepresentation = {
id: string;
};
type EventListenersFormProps = {
form: UseFormReturn;
@ -15,8 +26,24 @@ export const EventListenersForm = ({
}: EventListenersFormProps) => {
const { t } = useTranslation();
const serverInfo = useServerInfo();
const eventListeners = serverInfo.providers?.eventsListener.providers;
const [eventListeners, setEventListeners] =
useState<EventListenerRepresentation[]>();
const { adminClient } = useAdminClient();
useFetch(
() =>
fetchAdminUI<EventListenerRepresentation[]>(
adminClient,
"ui-ext/available-event-listeners",
),
setEventListeners,
[],
);
if (!eventListeners) {
return <KeycloakSpinner />;
}
return (
<FormProvider {...form}>
@ -34,7 +61,7 @@ export const EventListenersForm = ({
collapsedText: t("showRemaining"),
}}
variant={SelectVariant.typeaheadMulti}
options={Object.keys(eventListeners!)}
options={eventListeners.map((value) => value.id)}
/>
<ActionGroup>
<Button

View File

@ -35,6 +35,11 @@ public final class AdminExtResource {
return new AvailableRoleMappingResource(session, realm, auth);
}
@Path("/available-event-listeners")
public AvailableEventListenersResource availableEventListeners() {
return new AvailableEventListenersResource(session, auth);
}
@Path("/effective-roles")
public EffectiveRoleMappingResource effectiveRoles() {
return new EffectiveRoleMappingResource(session, realm, auth);

View File

@ -0,0 +1,82 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.ui.rest;
import java.util.ArrayList;
import java.util.List;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.keycloak.admin.ui.rest.model.EventListener;
import org.keycloak.admin.ui.rest.model.ProviderMapper;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
public class AvailableEventListenersResource {
private final KeycloakSession session;
private final AdminPermissionEvaluator auth;
public AvailableEventListenersResource(KeycloakSession session, AdminPermissionEvaluator auth) {
this.session = session;
this.auth = auth;
}
@GET
@Path("/")
@Produces({"application/json"})
@Operation(
summary = "List all available event listener providers",
description = "This endpoint returns List all available event listener providers"
)
@APIResponse(
responseCode = "200",
description = "",
content = {@Content(
schema = @Schema(
implementation = EventListener.class,
type = SchemaType.ARRAY
)
)}
)
public final List<EventListener> listAvailableEventListeners() {
auth.realm().requireViewEvents();
this.auth.adminAuth().getRealm().getEventsListenersStream();
ArrayList<EventListener> result = new ArrayList<>();
session.getKeycloakSessionFactory().getProviderFactoriesStream(EventListenerProvider.class).forEach(
providerFactory -> {
if (!((EventListenerProviderFactory) providerFactory).isGlobal()) {
result.add(ProviderMapper.convertToModel((EventListenerProviderFactory) providerFactory));
}
}
);
return result;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.ui.rest.model;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
public class EventListener {
@Schema(required = true)
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.admin.ui.rest.model;
import org.keycloak.events.EventListenerProviderFactory;
public class ProviderMapper {
public static EventListener convertToModel(EventListenerProviderFactory eventListenerProviderFactory) {
EventListener eventListener = new EventListener();
eventListener.setId(eventListenerProviderFactory.getId());
return eventListener;
}
}

View File

@ -16,10 +16,14 @@
*/
package org.keycloak.services.managers;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.core.Response;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.Encode;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.AbstractKeycloakTransaction;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.AdminRoles;
@ -296,6 +300,15 @@ public class RealmManager {
realm.setEventsEnabled(rep.isEventsEnabled());
realm.setEventsExpiration(rep.getEventsExpiration() != null ? rep.getEventsExpiration() : 0);
if (rep.getEventsListeners() != null) {
for (String el : rep.getEventsListeners()) {
EventListenerProviderFactory elpf = (EventListenerProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(EventListenerProvider.class, el);
if (elpf == null) {
throw new ClientErrorException("Unknown event listener", Response.Status.BAD_REQUEST);
}
if (elpf.isGlobal()) {
throw new ClientErrorException("Global event listeners not allowed in realm specific configuration", Response.Status.BAD_REQUEST);
}
}
realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
}
if(rep.getEnabledEventTypes() != null) {