Skip to content

Commit

Permalink
Don't show global event listeners in the admin UI
Browse files Browse the repository at this point in the history
Closes keycloak#34602

Signed-off-by: Alexander Schwartz <[email protected]>
  • Loading branch information
ahus1 authored and mhajas committed Nov 18, 2024
1 parent de14e1c commit f4a208d
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}>
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public AvailableRoleMappingResource availableRoles() {
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -296,6 +300,15 @@ public void updateRealmEventsConfig(RealmEventsConfigRepresentation rep, RealmMo
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) {
Expand Down

0 comments on commit f4a208d

Please sign in to comment.