Add organization admin crud events

Closes #31421

Signed-off-by: Maksim Zvankovich <m.zvankovich@rheagroup.com>
Co-authored-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Maksim Zvankovich 2024-08-22 20:18:47 +02:00 committed by Alexander Schwartz
parent e2810b788e
commit 90dc7c168c
7 changed files with 55 additions and 8 deletions

View file

@ -10,6 +10,7 @@ import { toClientScope } from "../client-scopes/routes/ClientScope";
import { toUser } from "../user/routes/User"; import { toUser } from "../user/routes/User";
import { toRealmRole } from "../realm-roles/routes/RealmRole"; import { toRealmRole } from "../realm-roles/routes/RealmRole";
import { toFlow } from "../authentication/routes/Flow"; import { toFlow } from "../authentication/routes/Flow";
import { toEditOrganization } from "../organizations/routes/EditOrganization";
type ResourceLinkProps = { type ResourceLinkProps = {
event: AdminEventRepresentation; event: AdminEventRepresentation;
@ -42,6 +43,8 @@ const isLinkable = (event: AdminEventRepresentation) => {
event.resourceType === "GROUP_MEMBERSHIP" || event.resourceType === "GROUP_MEMBERSHIP" ||
event.resourceType === "GROUP" || event.resourceType === "GROUP" ||
event.resourceType === "CLIENT" || event.resourceType === "CLIENT" ||
event.resourceType === "ORGANIZATION" ||
event.resourceType === "ORGANIZATION_MEMBERSHIP" ||
event.resourceType?.startsWith("AUTHORIZATION_RESOURCE") || event.resourceType?.startsWith("AUTHORIZATION_RESOURCE") ||
event.resourceType === "CLIENT_SCOPE" || event.resourceType === "CLIENT_SCOPE" ||
event.resourceType === "AUTH_FLOW" || event.resourceType === "AUTH_FLOW" ||
@ -95,6 +98,14 @@ const createLink = (realm: string, event: AdminEventRepresentation) => {
return toRealmRole({ realm, id, tab: "details" }); return toRealmRole({ realm, id, tab: "details" });
} }
if (event.resourceType === "ORGANIZATION") {
return toEditOrganization({ realm, id, tab: "settings" });
}
if (event.resourceType === "ORGANIZATION_MEMBERSHIP") {
return toEditOrganization({ realm, id, tab: "members" });
}
return ""; return "";
}; };

View file

@ -51,7 +51,6 @@ export class Organizations extends Resource<{ realm?: string }> {
public create = this.makeRequest<OrganizationRepresentation, { id: string }>({ public create = this.makeRequest<OrganizationRepresentation, { id: string }>({
method: "POST", method: "POST",
path: "/",
returnResourceIdInLocationHeader: { field: "id" }, returnResourceIdInLocationHeader: { field: "id" },
}); });

View file

@ -196,5 +196,15 @@ public enum ResourceType {
/** /**
* The user profile configuration * The user profile configuration
*/ */
, USER_PROFILE; , USER_PROFILE
/**
*
*/
, ORGANIZATION
/**
*
*/
, ORGANIZATION_MEMBERSHIP;
} }

View file

@ -28,6 +28,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.email.EmailException; import org.keycloak.email.EmailException;
import org.keycloak.email.EmailTemplateProvider; import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
@ -59,7 +60,7 @@ public class OrganizationInvitationResource {
this.session = session; this.session = session;
this.realm = session.getContext().getRealm(); this.realm = session.getContext().getRealm();
this.organization = organization; this.organization = organization;
this.adminEvent = adminEvent; this.adminEvent = adminEvent.resource(ResourceType.ORGANIZATION_MEMBERSHIP);
this.tokenExpiration = getTokenExpiration(); this.tokenExpiration = getTokenExpiration();
} }

View file

@ -40,6 +40,8 @@ import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.NoCache; import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
@ -79,7 +81,7 @@ public class OrganizationMemberResource {
this.realm = session.getContext().getRealm(); this.realm = session.getContext().getRealm();
this.provider = session.getProvider(OrganizationProvider.class); this.provider = session.getProvider(OrganizationProvider.class);
this.organization = organization; this.organization = organization;
this.adminEvent = adminEvent; this.adminEvent = adminEvent.resource(ResourceType.ORGANIZATION_MEMBERSHIP);
} }
@POST @POST
@ -97,6 +99,10 @@ public class OrganizationMemberResource {
try { try {
if (provider.addMember(organization, user)) { if (provider.addMember(organization, user)) {
adminEvent.operation(OperationType.CREATE).resource(ResourceType.ORGANIZATION_MEMBERSHIP)
.representation(ModelToRepresentation.toRepresentation(organization))
.resourcePath(session.getContext().getUri())
.success();
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(user.getId()).build()).build(); return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(user.getId()).build()).build();
} }
} catch (ModelException me) { } catch (ModelException me) {
@ -171,6 +177,10 @@ public class OrganizationMemberResource {
UserModel member = getMember(id); UserModel member = getMember(id);
if (provider.removeMember(organization, member)) { if (provider.removeMember(organization, member)) {
adminEvent.operation(OperationType.DELETE).resource(ResourceType.ORGANIZATION_MEMBERSHIP)
.representation(ModelToRepresentation.toRepresentation(organization))
.resourcePath(session.getContext().getUri())
.success();
return Response.noContent().build(); return Response.noContent().build();
} }

View file

@ -25,11 +25,14 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.ext.Provider;
import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension; import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.resteasy.reactive.NoCache; import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelValidationException; import org.keycloak.models.ModelValidationException;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
@ -59,7 +62,7 @@ public class OrganizationResource {
this.session = session; this.session = session;
this.provider = session == null ? null : session.getProvider(OrganizationProvider.class); this.provider = session == null ? null : session.getProvider(OrganizationProvider.class);
this.organization = organization; this.organization = organization;
this.adminEvent = adminEvent; this.adminEvent = adminEvent.resource(ResourceType.ORGANIZATION);
} }
@GET @GET
@ -75,8 +78,13 @@ public class OrganizationResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS) @Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
@Operation(summary = "Deletes the organization") @Operation(summary = "Deletes the organization")
public Response delete() { public Response delete() {
provider.remove(organization); boolean removed = provider.remove(organization);
return Response.noContent().build(); if (removed) {
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
return Response.noContent().build();
} else {
throw ErrorResponse.error("organization couldn't be deleted", Status.BAD_REQUEST);
}
} }
@PUT @PUT
@ -86,6 +94,7 @@ public class OrganizationResource {
public Response update(OrganizationRepresentation organizationRep) { public Response update(OrganizationRepresentation organizationRep) {
try { try {
RepresentationToModel.toModel(organizationRep, organization); RepresentationToModel.toModel(organizationRep, organization);
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(organizationRep).success();
return Response.noContent().build(); return Response.noContent().build();
} catch (ModelValidationException mve) { } catch (ModelValidationException mve) {
throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST); throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST);

View file

@ -37,7 +37,10 @@ import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension; import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.NoCache; import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelValidationException; import org.keycloak.models.ModelValidationException;
@ -64,6 +67,8 @@ public class OrganizationsResource {
private final AdminPermissionEvaluator auth; private final AdminPermissionEvaluator auth;
private final AdminEventBuilder adminEvent; private final AdminEventBuilder adminEvent;
private static final Logger logger = Logger.getLogger(OrganizationsResource.class);
public OrganizationsResource() { public OrganizationsResource() {
// needed for registering to the JAX-RS stack // needed for registering to the JAX-RS stack
this(null, null, null); this(null, null, null);
@ -73,7 +78,7 @@ public class OrganizationsResource {
this.session = session; this.session = session;
this.provider = session == null ? null : session.getProvider(OrganizationProvider.class); this.provider = session == null ? null : session.getProvider(OrganizationProvider.class);
this.auth = auth; this.auth = auth;
this.adminEvent = adminEvent; this.adminEvent = adminEvent.resource(ResourceType.ORGANIZATION);
} }
/** /**
@ -99,6 +104,8 @@ public class OrganizationsResource {
try { try {
OrganizationModel model = provider.create(organization.getName(), organization.getAlias()); OrganizationModel model = provider.create(organization.getName(), organization.getAlias());
RepresentationToModel.toModel(organization, model); RepresentationToModel.toModel(organization, model);
organization.setId(model.getId());
adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), model.getId()).representation(organization).success();
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build(); return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build();
} catch (ModelValidationException mve) { } catch (ModelValidationException mve) {
throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST); throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST);