keycloak-developers
Changes
events/jboss-logging/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java 83(+61 -22)
examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProvider.java 40(+39 -1)
examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProviderFactory.java 12(+11 -1)
examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemAdminEventQuery.java 162(+162 -0)
examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java 53(+52 -1)
examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProviderFactory.java 17(+15 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js 120(+117 -3)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-auth.html 8(+8 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-representation.html 3(+3 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events.html 5(+3 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html 128(+128 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html 149(+100 -49)
forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java 37(+37 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 20(+20 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 37(+37 -0)
services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java 15(+13 -2)
services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java 34(+27 -7)
services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java 379(+197 -182)
services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java 21(+19 -2)
services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java 16(+14 -2)
services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java 41(+26 -15)
Details
diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 2b94243..ad41016 100755
--- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
public String FIRST_VERSION = "1.0.0.Final";
- public String LAST_VERSION = "1.2.0.RC1";
+ public String LAST_VERSION = "1.3.0.Beta1";
public String getCurrentVersionSql();
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 4dbad93..79b730a 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -34,9 +34,10 @@
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
- <!-- JpaAuditProvider -->
+ <!-- JpaAuditProviders -->
<class>org.keycloak.events.jpa.EventEntity</class>
-
+ <class>org.keycloak.events.jpa.AdminEventEntity</class>
+
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
new file mode 100644
index 0000000..7086065
--- /dev/null
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+ <changeSet author="giriraj.sharma27@gmail.com" id="1.3.0.Beta1">
+ <createTable tableName="ADMIN_EVENT_ENTITY">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="ADMIN_EVENT_TIME" type="BIGINT"/>
+ <column name="OPERATION_TYPE" type="VARCHAR(255)"/>
+ <column name="REALM_ID" type="VARCHAR(255)"/>
+ <column name="CLIENT_ID" type="VARCHAR(255)"/>
+ <column name="USER_ID" type="VARCHAR(255)"/>
+ <column name="IP_ADDRESS" type="VARCHAR(255)"/>
+ <column name="RESOURCE_PATH" type="VARCHAR(255)"/>
+ <column name="REPRESENTATION" type="VARCHAR(25500)"/>
+ <column name="ERROR" type="VARCHAR(255)"/>
+ </createTable>
+ <createTable tableName="REALM_ENABLED_ADMIN_EVENT_OPERATIONS">
+ <column name="REALM_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="VALUE" type="VARCHAR(255)"/>
+ </createTable>
+ <addColumn tableName="REALM">
+ <column name="ADMIN_EVENTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="ADMIN_EVENTS_DETAILS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_ENABLED_ADMIN_EVENT_OPERATIONS" constraintName="FKF8459C8FAE5C3B34" referencedColumnNames="ID" referencedTableName="REALM"/>
+ </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
index 33eb5a1..8272af8 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -5,4 +5,5 @@
<include file="META-INF/jpa-changelog-1.1.0.Final.xml"/>
<include file="META-INF/jpa-changelog-1.2.0.Beta1.xml"/>
<include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/>
+ <include file="META-INF/jpa-changelog-1.3.0.Beta1.xml"/>
</databaseChangeLog>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmEventsConfigRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmEventsConfigRepresentation.java
index a88a75d..5b12c10 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmEventsConfigRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmEventsConfigRepresentation.java
@@ -11,6 +11,10 @@ public class RealmEventsConfigRepresentation {
protected Long eventsExpiration;
protected List<String> eventsListeners;
protected List<String> enabledEventTypes;
+
+ protected Boolean adminEventsEnabled;
+ protected List<String> adminEnabledEventOperations;
+ protected Boolean adminEventsDetailsEnabled;
public boolean isEventsEnabled() {
return eventsEnabled;
@@ -43,4 +47,29 @@ public class RealmEventsConfigRepresentation {
public void setEnabledEventTypes(List<String> enabledEventTypes) {
this.enabledEventTypes = enabledEventTypes;
}
+
+ public Boolean isAdminEventsEnabled() {
+ return adminEventsEnabled;
+ }
+
+ public void setAdminEventsEnabled(Boolean adminEventsEnabled) {
+ this.adminEventsEnabled = adminEventsEnabled;
+ }
+
+ public List<String> getAdminEnabledEventOperations() {
+ return adminEnabledEventOperations;
+ }
+
+ public void setAdminEnabledEventOperations(List<String> adminEnabledEventOperations) {
+ this.adminEnabledEventOperations = adminEnabledEventOperations;
+ }
+
+ public Boolean isAdminEventsDetailsEnabled() {
+ return adminEventsDetailsEnabled;
+ }
+
+ public void setAdminEventsDetailsEnabled(Boolean adminEventsDetailsEnabled) {
+ this.adminEventsDetailsEnabled = adminEventsDetailsEnabled;
+ }
+
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index d9926fe..2c9be55 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -57,10 +57,16 @@ public class RealmRepresentation {
protected String accountTheme;
protected String adminTheme;
protected String emailTheme;
+
protected Boolean eventsEnabled;
protected Long eventsExpiration;
protected List<String> eventsListeners;
protected List<String> enabledEventTypes;
+
+ protected Boolean adminEventsEnabled;
+ protected List<String> adminEnabledEventOperations;
+ protected Boolean adminEventsDetailsEnabled;
+
private List<IdentityProviderRepresentation> identityProviders;
private List<IdentityProviderMapperRepresentation> identityProviderMappers;
private List<ProtocolMapperRepresentation> protocolMappers;
@@ -507,6 +513,30 @@ public class RealmRepresentation {
this.enabledEventTypes = enabledEventTypes;
}
+ public Boolean isAdminEventsEnabled() {
+ return adminEventsEnabled;
+ }
+
+ public void setAdminEventsEnabled(Boolean adminEventsEnabled) {
+ this.adminEventsEnabled = adminEventsEnabled;
+ }
+
+ public List<String> getAdminEnabledEventOperations() {
+ return adminEnabledEventOperations;
+ }
+
+ public void setAdminEnabledEventOperations(List<String> adminEnabledEventOperations) {
+ this.adminEnabledEventOperations = adminEnabledEventOperations;
+ }
+
+ public Boolean isAdminEventsDetailsEnabled() {
+ return adminEventsDetailsEnabled;
+ }
+
+ public void setAdminEventsDetailsEnabled(Boolean adminEventsDetailsEnabled) {
+ this.adminEventsDetailsEnabled = adminEventsDetailsEnabled;
+ }
+
public List<UserFederationProviderRepresentation> getUserFederationProviders() {
return userFederationProviders;
}
diff --git a/events/api/src/main/java/org/keycloak/events/admin/AdminEvent.java b/events/api/src/main/java/org/keycloak/events/admin/AdminEvent.java
new file mode 100644
index 0000000..fedd2bf
--- /dev/null
+++ b/events/api/src/main/java/org/keycloak/events/admin/AdminEvent.java
@@ -0,0 +1,106 @@
+package org.keycloak.events.admin;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AdminEvent {
+
+ private long time;
+
+ private AuthDetails authDetails;
+
+ private OperationType operationType;
+
+ private String resourcePath;
+
+ private String representation;
+
+ private String error;
+
+ /**
+ * Returns the time of the event
+ *
+ * @return time in millis
+ */
+ public long getTime() {
+ return time;
+ }
+
+ public void setTime(long time) {
+ this.time = time;
+ }
+
+ /**
+ * Returns authentication details
+ *
+ * @return
+ */
+ public AuthDetails getAuthDetails() {
+ return authDetails;
+ }
+
+ public void setAuthDetails(AuthDetails authDetails) {
+ this.authDetails = authDetails;
+ }
+
+ /**
+ * Returns the type of the operation
+ *
+ * @return
+ */
+ public OperationType getOperationType() {
+ return operationType;
+ }
+
+ public void setOperationType(OperationType operationType) {
+ this.operationType = operationType;
+ }
+
+ /**
+ * Returns the path of the resource. For example:
+ * <ul>
+ * <li><b>realms</b> - realm list</li>
+ * <li><b>realms/master</b> - master realm</li>
+ * <li><b>realms/clients/00d4b16f-f1f9-4e73-8366-d76b18f3e0e1</b> - client within the master realm</li>
+ * </ul>
+ *
+ * @return
+ */
+ public String getResourcePath() {
+ return resourcePath;
+ }
+
+ public void setResourcePath(String resourcePath) {
+ this.resourcePath = resourcePath;
+ }
+
+ /**
+ * Returns the updated JSON representation if <code>operationType</code> is <code>CREATE</code> or <code>UPDATE</code>.
+ * Otherwise returns <code>null</code>.
+ *
+ * @return
+ */
+ public String getRepresentation() {
+ return representation;
+ }
+
+ public void setRepresentation(String representation) {
+ this.representation = representation;
+ }
+
+ /**
+ * If the event was unsuccessful returns the error message. Otherwise returns <code>null</code>.
+ *
+ * @return
+ */
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+}
diff --git a/events/api/src/main/java/org/keycloak/events/admin/AdminEventQuery.java b/events/api/src/main/java/org/keycloak/events/admin/AdminEventQuery.java
new file mode 100644
index 0000000..45cd9e2
--- /dev/null
+++ b/events/api/src/main/java/org/keycloak/events/admin/AdminEventQuery.java
@@ -0,0 +1,102 @@
+package org.keycloak.events.admin;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AdminEventQuery {
+
+ /**
+ * Search by authentication realm
+ *
+ * @param realm realm name
+ * @return Associated <code>AdminEventQuery</code> for method chaining
+ */
+ AdminEventQuery authRealm(String realm);
+
+ /**
+ * Search by authenticated client
+ *
+ * @param client client uuid
+ * @return Associated <code>AdminEventQuery</code> for method chaining
+ */
+ AdminEventQuery authClient(String client);
+
+ /**
+ * Search by authenticated user
+ *
+ * @param user user uuid
+ * @return Associated <code>AdminEventQuery</code> for method chaining
+ */
+ AdminEventQuery authUser(String user);
+
+ /**
+ * Search by request ip address
+ *
+ * @param ipAddress
+ * @return Associated <code>AdminEventQuery</code> for method chaining
+ */
+ AdminEventQuery authIpAddress(String ipAddress);
+
+ /**
+ * Search by operation type
+ *
+ * @param operations
+ * @return <code>this</code> for method chaining
+ */
+ AdminEventQuery operation(OperationType... operations);
+
+ /**
+ * Search by resource path. Supports wildcards <code>*</code> and <code>**</code>. For example:
+ * <ul>
+ * <li><b>*/master</b> - matches 'realms/master'</li>
+ * <li><b>**/00d4b16f</b> - matches 'realms/master/clients/00d4b16f'</li>
+ * <li><b>realms/master/**</b> - matches anything under 'realms/master'</li>
+ * </ul>
+ *
+ * @param resourcePath
+ * @return <code>this</code> for method chaining
+ */
+ AdminEventQuery resourcePath(String resourcePath);
+
+ /**
+ * Search by events after the specified time
+ *
+ * @param fromTime time in millis
+ * @return <code>this</code> for method chaining
+ */
+ AdminEventQuery fromTime(String fromTime);
+
+ /**
+ * Search by events before the specified time
+ *
+ * @param toTime time in millis
+ * @return <code>this</code> for method chaining
+ */
+ AdminEventQuery toTime(String toTime);
+
+ /**
+ * Used for pagination
+ *
+ * @param first first result to return
+ * @return <code>this</code> for method chaining
+ */
+ AdminEventQuery firstResult(int first);
+
+ /**
+ * Use for pagination
+ *
+ * @param max the maximum results to return
+ * @return <code>this</code> for method chaining
+ */
+ AdminEventQuery maxResults(int max);
+
+ /**
+ * Executes the query and returns the results
+ *
+ * @return
+ */
+ List<AdminEvent> getResultList();
+
+}
diff --git a/events/api/src/main/java/org/keycloak/events/admin/AuthDetails.java b/events/api/src/main/java/org/keycloak/events/admin/AuthDetails.java
new file mode 100644
index 0000000..d32eff1
--- /dev/null
+++ b/events/api/src/main/java/org/keycloak/events/admin/AuthDetails.java
@@ -0,0 +1,48 @@
+package org.keycloak.events.admin;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AuthDetails {
+
+ private String realmId;
+
+ private String clientId;
+
+ private String userId;
+
+ private String ipAddress;
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+}
diff --git a/events/api/src/main/java/org/keycloak/events/admin/AuthQuery.java b/events/api/src/main/java/org/keycloak/events/admin/AuthQuery.java
new file mode 100644
index 0000000..3be431f
--- /dev/null
+++ b/events/api/src/main/java/org/keycloak/events/admin/AuthQuery.java
@@ -0,0 +1,48 @@
+package org.keycloak.events.admin;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AuthQuery {
+
+ private String realmId;
+
+ private String clientId;
+
+ private String userId;
+
+ private String ipAddress;
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+}
diff --git a/events/api/src/main/java/org/keycloak/events/admin/OperationType.java b/events/api/src/main/java/org/keycloak/events/admin/OperationType.java
new file mode 100755
index 0000000..ddce0e2
--- /dev/null
+++ b/events/api/src/main/java/org/keycloak/events/admin/OperationType.java
@@ -0,0 +1,24 @@
+package org.keycloak.events.admin;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public enum OperationType {
+
+ VIEW(false),
+ CREATE(true),
+ UPDATE(true),
+ DELETE(true),
+ ACTION(false);
+
+ private boolean saveByDefault;
+
+ OperationType(boolean saveByDefault) {
+ this.saveByDefault = saveByDefault;
+ }
+
+ public boolean isSaveByDefault() {
+ return saveByDefault;
+ }
+
+}
diff --git a/events/api/src/main/java/org/keycloak/events/AdminEventBuilder.java b/events/api/src/main/java/org/keycloak/events/AdminEventBuilder.java
new file mode 100644
index 0000000..e822217
--- /dev/null
+++ b/events/api/src/main/java/org/keycloak/events/AdminEventBuilder.java
@@ -0,0 +1,205 @@
+package org.keycloak.events;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+import org.keycloak.ClientConnection;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AuthDetails;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.Time;
+
+public class AdminEventBuilder {
+
+ private static final Logger log = Logger.getLogger(AdminEventBuilder.class);
+
+ private EventStoreProvider store;
+ private List<EventListenerProvider> listeners;
+ private RealmModel realm;
+ private AdminEvent adminEvent;
+
+ public AdminEventBuilder(RealmModel realm, KeycloakSession session, ClientConnection clientConnection) {
+ this.realm = realm;
+
+ adminEvent = new AdminEvent();
+
+ if (realm.isAdminEventsEnabled()) {
+ EventStoreProvider store = session.getProvider(EventStoreProvider.class);
+ if (store != null) {
+ this.store = store;
+ } else {
+ log.error("Admin Events enabled, but no event store provider configured");
+ }
+ }
+
+ if (realm.getEventsListeners() != null && !realm.getEventsListeners().isEmpty()) {
+ this.listeners = new LinkedList<>();
+ for (String id : realm.getEventsListeners()) {
+ EventListenerProvider listener = session.getProvider(EventListenerProvider.class, id);
+ if (listener != null) {
+ listeners.add(listener);
+ } else {
+ log.error("Event listener '" + id + "' registered, but provider not found");
+ }
+ }
+ }
+
+ realm(realm);
+ ipAddress(clientConnection.getRemoteAddr());
+ }
+
+ public AdminEventBuilder operation(OperationType e) {
+ adminEvent.setOperationType(e);
+ return this;
+ }
+
+ public AdminEventBuilder realm(RealmModel realm) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setRealmId(realm.getId());
+ } else {
+ authDetails.setRealmId(realm.getId());
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder realm(String realmId) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setRealmId(realmId);
+ } else {
+ authDetails.setRealmId(realmId);
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder client(ClientModel client) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setClientId(client.getId());
+ } else {
+ authDetails.setClientId(client.getId());
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder client(String clientId) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setClientId(clientId);
+ } else {
+ authDetails.setClientId(clientId);
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder user(UserModel user) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setUserId(user.getId());
+ } else {
+ authDetails.setUserId(user.getId());
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder user(String userId) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setUserId(userId);
+ } else {
+ authDetails.setUserId(userId);
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder ipAddress(String ipAddress) {
+ AuthDetails authDetails = adminEvent.getAuthDetails();
+ if(authDetails == null) {
+ authDetails = new AuthDetails();
+ authDetails.setIpAddress(ipAddress);
+ } else {
+ authDetails.setIpAddress(ipAddress);
+ }
+ adminEvent.setAuthDetails(authDetails);
+ return this;
+ }
+
+ public AdminEventBuilder resourcePath(String resourcePath) {
+ adminEvent.setResourcePath(resourcePath);
+ return this;
+ }
+
+ public void error(String error) {
+ adminEvent.setOperationType(OperationType.valueOf(adminEvent.getOperationType().name() + "_ERROR"));
+ adminEvent.setError(error);
+ send();
+ }
+
+ public AdminEventBuilder representation(Object value) {
+ if (value == null || value.equals("")) {
+ return this;
+ }
+ try {
+ adminEvent.setRepresentation(JsonSerialization.writeValueAsString(value));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return this;
+ }
+
+ public AdminEvent getEvent() {
+ return adminEvent;
+ }
+
+ public void success() {
+ send();
+ }
+
+ private void send() {
+ boolean includeRepresentation = false;
+ if(realm.isAdminEventsDetailsEnabled()) {
+ includeRepresentation = true;
+ }
+ adminEvent.setTime(Time.toMillis(Time.currentTime()));
+
+ if (store != null) {
+ if (realm.getAdminEnabledEventOperations() != null && !realm.getAdminEnabledEventOperations().isEmpty() ? realm.getAdminEnabledEventOperations().contains(adminEvent.getOperationType().name()) : adminEvent.getOperationType().isSaveByDefault()) {
+ try {
+ store.onEvent(adminEvent, includeRepresentation);
+ } catch (Throwable t) {
+ log.error("Failed to save event", t);
+ }
+ }
+ }
+
+ if (listeners != null) {
+ for (EventListenerProvider l : listeners) {
+ try {
+ l.onEvent(adminEvent, includeRepresentation);
+ } catch (Throwable t) {
+ log.error("Failed to send type to " + l, t);
+ }
+ }
+ }
+ }
+}
diff --git a/events/api/src/main/java/org/keycloak/events/EventBuilder.java b/events/api/src/main/java/org/keycloak/events/EventBuilder.java
index 4945989..ee7a70a 100644
--- a/events/api/src/main/java/org/keycloak/events/EventBuilder.java
+++ b/events/api/src/main/java/org/keycloak/events/EventBuilder.java
@@ -7,6 +7,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.util.Time;
import java.util.HashMap;
import java.util.LinkedList;
@@ -149,7 +150,7 @@ public class EventBuilder {
}
private void send() {
- event.setTime(System.currentTimeMillis());
+ event.setTime(Time.toMillis(Time.currentTime()));
if (store != null) {
if (realm.getEnabledEventTypes() != null && !realm.getEnabledEventTypes().isEmpty() ? realm.getEnabledEventTypes().contains(event.getType().name()) : event.getType().isSaveByDefault()) {
diff --git a/events/api/src/main/java/org/keycloak/events/EventListenerProvider.java b/events/api/src/main/java/org/keycloak/events/EventListenerProvider.java
index 01ab302..957d639 100644
--- a/events/api/src/main/java/org/keycloak/events/EventListenerProvider.java
+++ b/events/api/src/main/java/org/keycloak/events/EventListenerProvider.java
@@ -1,5 +1,6 @@
package org.keycloak.events;
+import org.keycloak.events.admin.AdminEvent;
import org.keycloak.provider.Provider;
/**
@@ -9,4 +10,6 @@ public interface EventListenerProvider extends Provider {
public void onEvent(Event event);
+ public void onEvent(AdminEvent event, boolean includeRepresentation);
+
}
diff --git a/events/api/src/main/java/org/keycloak/events/EventStoreProvider.java b/events/api/src/main/java/org/keycloak/events/EventStoreProvider.java
index c742556..afdaf92 100644
--- a/events/api/src/main/java/org/keycloak/events/EventStoreProvider.java
+++ b/events/api/src/main/java/org/keycloak/events/EventStoreProvider.java
@@ -1,5 +1,7 @@
package org.keycloak.events;
+import org.keycloak.events.admin.AdminEventQuery;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -7,10 +9,18 @@ public interface EventStoreProvider extends EventListenerProvider {
public EventQuery createQuery();
+ public AdminEventQuery createAdminQuery();
+
public void clear();
public void clear(String realmId);
public void clear(String realmId, long olderThan);
+ public void clearAdmin();
+
+ public void clearAdmin(String realmId);
+
+ public void clearAdmin(String realmId, long olderThan);
+
}
diff --git a/events/email/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java b/events/email/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java
index 71a432a..400ef04 100755
--- a/events/email/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java
+++ b/events/email/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java
@@ -3,6 +3,7 @@ package org.keycloak.events.email;
import org.jboss.logging.Logger;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
+import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
@@ -50,6 +51,11 @@ public class EmailEventListenerProvider implements EventListenerProvider {
}
@Override
+ public void onEvent(AdminEvent event, boolean includeRepresentation) {
+
+ }
+
+ @Override
public void close() {
}
diff --git a/events/jboss-logging/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java b/events/jboss-logging/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java
index dba4304..38283f7 100755
--- a/events/jboss-logging/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java
+++ b/events/jboss-logging/src/main/java/org/keycloak/events/log/JBossLoggingEventListenerProvider.java
@@ -1,6 +1,7 @@
package org.keycloak.events.log;
import org.jboss.logging.Logger;
+import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.models.KeycloakContext;
@@ -9,8 +10,8 @@ import org.keycloak.models.KeycloakSession;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
+
import java.util.Map;
-import java.util.logging.Level;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -66,29 +67,42 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
}
}
}
+
+ if(logger.isTraceEnabled()) {
+ setKeycloakContext(sb);
+ }
- if (logger.isTraceEnabled()) {
- KeycloakContext context = session.getContext();
- UriInfo uriInfo = context.getUri();
- HttpHeaders headers = context.getRequestHeaders();
- if (uriInfo != null) {
- sb.append(", requestUri=");
- sb.append(uriInfo.getRequestUri().toString());
- }
+ logger.log(logger.isTraceEnabled() ? Logger.Level.TRACE : level, sb.toString());
+ }
+ }
- if (headers != null) {
- sb.append(", cookies=[");
- boolean f = true;
- for (Map.Entry<String, Cookie> e : headers.getCookies().entrySet()) {
- if (f) {
- f = false;
- } else {
- sb.append(", ");
- }
- sb.append(e.getValue().toString());
- }
- sb.append("]");
- }
+ @Override
+ public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
+ Logger.Level level = adminEvent.getError() != null ? errorLevel : successLevel;
+
+ if (logger.isEnabled(level)) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("operationType=");
+ sb.append(adminEvent.getOperationType());
+ sb.append(", realmId=");
+ sb.append(adminEvent.getAuthDetails().getRealmId());
+ sb.append(", clientId=");
+ sb.append(adminEvent.getAuthDetails().getClientId());
+ sb.append(", userId=");
+ sb.append(adminEvent.getAuthDetails().getUserId());
+ sb.append(", ipAddress=");
+ sb.append(adminEvent.getAuthDetails().getIpAddress());
+ sb.append(", resourcePath=");
+ sb.append(adminEvent.getResourcePath());
+
+ if (adminEvent.getError() != null) {
+ sb.append(", error=");
+ sb.append(adminEvent.getError());
+ }
+
+ if(logger.isTraceEnabled()) {
+ setKeycloakContext(sb);
}
logger.log(logger.isTraceEnabled() ? Logger.Level.TRACE : level, sb.toString());
@@ -98,5 +112,30 @@ public class JBossLoggingEventListenerProvider implements EventListenerProvider
@Override
public void close() {
}
+
+ private void setKeycloakContext(StringBuilder sb) {
+ KeycloakContext context = session.getContext();
+ UriInfo uriInfo = context.getUri();
+ HttpHeaders headers = context.getRequestHeaders();
+ if (uriInfo != null) {
+ sb.append(", requestUri=");
+ sb.append(uriInfo.getRequestUri().toString());
+ }
+
+ if (headers != null) {
+ sb.append(", cookies=[");
+ boolean f = true;
+ for (Map.Entry<String, Cookie> e : headers.getCookies().entrySet()) {
+ if (f) {
+ f = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(e.getValue().toString());
+ }
+ sb.append("]");
+ }
+
+ }
}
diff --git a/events/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java b/events/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java
new file mode 100644
index 0000000..f45cd32
--- /dev/null
+++ b/events/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java
@@ -0,0 +1,126 @@
+package org.keycloak.events.jpa;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
+ */
+@Entity
+@Table(name="ADMIN_EVENT_ENTITY")
+public class AdminEventEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ private String id;
+
+ @Column(name="ADMIN_EVENT_TIME")
+ private long time;
+
+ @Column(name="OPERATION_TYPE")
+ private String operationType;
+
+ @Column(name="REALM_ID")
+ private String authRealmId;
+
+ @Column(name="CLIENT_ID")
+ private String authClientId;
+
+ @Column(name="USER_ID")
+ private String authUserId;
+
+ @Column(name="IP_ADDRESS")
+ private String authIpAddress;
+
+ @Column(name="RESOURCE_PATH")
+ private String resourcePath;
+
+ @Column(name="REPRESENTATION", length = 25500)
+ private String representation;
+
+ @Column(name="ERROR")
+ private String error;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public long getTime() {
+ return time;
+ }
+
+ public void setTime(long time) {
+ this.time = time;
+ }
+
+ public String getOperationType() {
+ return operationType;
+ }
+
+ public void setOperationType(String operationType) {
+ this.operationType = operationType;
+ }
+
+ public String getAuthRealmId() {
+ return authRealmId;
+ }
+
+ public void setAuthRealmId(String authRealmId) {
+ this.authRealmId = authRealmId;
+ }
+
+ public String getAuthClientId() {
+ return authClientId;
+ }
+
+ public void setAuthClientId(String authClientId) {
+ this.authClientId = authClientId;
+ }
+
+ public String getAuthUserId() {
+ return authUserId;
+ }
+
+ public void setAuthUserId(String authUserId) {
+ this.authUserId = authUserId;
+ }
+
+ public String getAuthIpAddress() {
+ return authIpAddress;
+ }
+
+ public void setAuthIpAddress(String authIpAddress) {
+ this.authIpAddress = authIpAddress;
+ }
+
+ public String getResourcePath() {
+ return resourcePath;
+ }
+
+ public void setResourcePath(String resourcePath) {
+ this.resourcePath = resourcePath;
+ }
+
+ public String getRepresentation() {
+ return representation;
+ }
+
+ public void setRepresentation(String representation) {
+ this.representation = representation;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+}
diff --git a/events/jpa/src/main/java/org/keycloak/events/jpa/JpaAdminEventQuery.java b/events/jpa/src/main/java/org/keycloak/events/jpa/JpaAdminEventQuery.java
new file mode 100644
index 0000000..edb19c0
--- /dev/null
+++ b/events/jpa/src/main/java/org/keycloak/events/jpa/JpaAdminEventQuery.java
@@ -0,0 +1,148 @@
+package org.keycloak.events.jpa;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.OperationType;
+
+/**
+ * @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
+ */
+public class JpaAdminEventQuery implements AdminEventQuery {
+
+ private final EntityManager em;
+ private final CriteriaBuilder cb;
+ private final CriteriaQuery<AdminEventEntity> cq;
+ private final Root<AdminEventEntity> root;
+ private final ArrayList<Predicate> predicates;
+ private Integer firstResult;
+ private Integer maxResults;
+
+ public JpaAdminEventQuery(EntityManager em) {
+ this.em = em;
+
+ cb = em.getCriteriaBuilder();
+ cq = cb.createQuery(AdminEventEntity.class);
+ root = cq.from(AdminEventEntity.class);
+ predicates = new ArrayList<Predicate>();
+ }
+
+ @Override
+ public AdminEventQuery operation(OperationType... operations) {
+ List<String> operationStrings = new LinkedList<String>();
+ for (OperationType e : operations) {
+ operationStrings.add(e.toString());
+ }
+ predicates.add(root.get("operationType").in(operationStrings));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authRealm(String realmId) {
+ predicates.add(cb.equal(root.get("authRealmId"), realmId));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authClient(String clientId) {
+ predicates.add(cb.equal(root.get("authClientId"), clientId));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authUser(String userId) {
+ predicates.add(cb.equal(root.get("authUserId"), userId));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authIpAddress(String ipAddress) {
+ predicates.add(cb.equal(root.get("authIpAddress"), ipAddress));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery resourcePath(String resourcePath) {
+ Expression<String> rPath = root.get("resourcePath");
+ predicates.add(cb.like(rPath, "%"+resourcePath+"%"));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery fromTime(String fromTime) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ Long from = null;
+ try {
+ from = df.parse(fromTime).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ predicates.add(cb.greaterThanOrEqualTo(root.<Long>get("time"), from));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery toTime(String toTime) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ Long to = null;
+ try {
+ to = df.parse(toTime).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ predicates.add(cb.lessThanOrEqualTo(root.<Long>get("time"), to));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery firstResult(int firstResult) {
+ this.firstResult = firstResult;
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery maxResults(int maxResults) {
+ this.maxResults = maxResults;
+ return this;
+ }
+
+ @Override
+ public List<AdminEvent> getResultList() {
+ if (!predicates.isEmpty()) {
+ cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
+ }
+
+ cq.orderBy(cb.desc(root.get("time")));
+
+ TypedQuery<AdminEventEntity> query = em.createQuery(cq);
+
+ if (firstResult != null) {
+ query.setFirstResult(firstResult);
+ }
+
+ if (maxResults != null) {
+ query.setMaxResults(maxResults);
+ }
+
+ List<AdminEvent> events = new LinkedList<AdminEvent>();
+ for (AdminEventEntity e : query.getResultList()) {
+ events.add(JpaEventStoreProvider.convertAdminEvent(e));
+ }
+
+ return events;
+ }
+
+}
diff --git a/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventQuery.java b/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventQuery.java
index 5e39d17..ffbf619 100644
--- a/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventQuery.java
+++ b/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventQuery.java
@@ -131,7 +131,7 @@ public class JpaEventQuery implements EventQuery {
List<Event> events = new LinkedList<Event>();
for (EventEntity e : query.getResultList()) {
- events.add(JpaEventStoreProvider.convert(e));
+ events.add(JpaEventStoreProvider.convertEvent(e));
}
return events;
diff --git a/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java b/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java
index 8fca6ec..3088f1e 100755
--- a/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java
+++ b/events/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java
@@ -3,12 +3,17 @@ package org.keycloak.events.jpa;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.jboss.logging.Logger;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.AuthDetails;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.events.EventType;
import javax.persistence.EntityManager;
+
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
@@ -51,49 +56,119 @@ public class JpaEventStoreProvider implements EventStoreProvider {
@Override
public void onEvent(Event event) {
- em.persist(convert(event));
+ em.persist(convertEvent(event));
+ }
+
+ @Override
+ public AdminEventQuery createAdminQuery() {
+ return new JpaAdminEventQuery(em);
+ }
+
+ @Override
+ public void clearAdmin() {
+ em.createQuery("delete from AdminEventEntity").executeUpdate();
+ }
+
+ @Override
+ public void clearAdmin(String authRealmId) {
+ em.createQuery("delete from AdminEventEntity where authRealmId = :authRealmId").setParameter("authRealmId", authRealmId).executeUpdate();
+ }
+
+ @Override
+ public void clearAdmin(String authRealmId, long olderThan) {
+ em.createQuery("delete from AdminEventEntity where authRealmId = :authRealmId and time < :time").setParameter("authRealmId", authRealmId).setParameter("time", olderThan).executeUpdate();
+ }
+
+ @Override
+ public void onEvent(AdminEvent event, boolean includeRepresentation) {
+ em.persist(convertAdminEvent(event, includeRepresentation));
}
@Override
public void close() {
}
- static EventEntity convert(Event o) {
- EventEntity e = new EventEntity();
- e.setId(UUID.randomUUID().toString());
- e.setTime(o.getTime());
- e.setType(o.getType().toString());
- e.setRealmId(o.getRealmId());
- e.setClientId(o.getClientId());
- e.setUserId(o.getUserId());
- e.setSessionId(o.getSessionId());
- e.setIpAddress(o.getIpAddress());
- e.setError(o.getError());
+ static EventEntity convertEvent(Event event) {
+ EventEntity eventEntity = new EventEntity();
+ eventEntity.setId(UUID.randomUUID().toString());
+ eventEntity.setTime(event.getTime());
+ eventEntity.setType(event.getType().toString());
+ eventEntity.setRealmId(event.getRealmId());
+ eventEntity.setClientId(event.getClientId());
+ eventEntity.setUserId(event.getUserId());
+ eventEntity.setSessionId(event.getSessionId());
+ eventEntity.setIpAddress(event.getIpAddress());
+ eventEntity.setError(event.getError());
try {
- e.setDetailsJson(mapper.writeValueAsString(o.getDetails()));
+ eventEntity.setDetailsJson(mapper.writeValueAsString(event.getDetails()));
} catch (IOException ex) {
logger.error("Failed to write log details", ex);
}
- return e;
- }
-
- static Event convert(EventEntity o) {
- Event e = new Event();
- e.setTime(o.getTime());
- e.setType(EventType.valueOf(o.getType()));
- e.setRealmId(o.getRealmId());
- e.setClientId(o.getClientId());
- e.setUserId(o.getUserId());
- e.setSessionId(o.getSessionId());
- e.setIpAddress(o.getIpAddress());
- e.setError(o.getError());
+ return eventEntity;
+ }
+
+ static Event convertEvent(EventEntity eventEntity) {
+ Event event = new Event();
+ event.setTime(eventEntity.getTime());
+ event.setType(EventType.valueOf(eventEntity.getType()));
+ event.setRealmId(eventEntity.getRealmId());
+ event.setClientId(eventEntity.getClientId());
+ event.setUserId(eventEntity.getUserId());
+ event.setSessionId(eventEntity.getSessionId());
+ event.setIpAddress(eventEntity.getIpAddress());
+ event.setError(eventEntity.getError());
try {
- Map<String, String> details = mapper.readValue(o.getDetailsJson(), mapType);
- e.setDetails(details);
+ Map<String, String> details = mapper.readValue(eventEntity.getDetailsJson(), mapType);
+ event.setDetails(details);
} catch (IOException ex) {
logger.error("Failed to read log details", ex);
}
- return e;
+ return event;
+ }
+
+ static AdminEventEntity convertAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) {
+ AdminEventEntity adminEventEntity = new AdminEventEntity();
+ adminEventEntity.setId(UUID.randomUUID().toString());
+ adminEventEntity.setTime(adminEvent.getTime());
+ setAuthDetails(adminEventEntity, adminEvent.getAuthDetails());
+ adminEventEntity.setOperationType(adminEvent.getOperationType().toString());
+ adminEventEntity.setResourcePath(adminEvent.getResourcePath());
+ adminEventEntity.setError(adminEvent.getError());
+
+ if(includeRepresentation) {
+ adminEventEntity.setRepresentation(adminEvent.getRepresentation());
+ }
+ return adminEventEntity;
+ }
+
+ static AdminEvent convertAdminEvent(AdminEventEntity adminEventEntity) {
+ AdminEvent adminEvent = new AdminEvent();
+ adminEvent.setTime(adminEventEntity.getTime());
+ setAuthDetails(adminEvent, adminEventEntity);
+ adminEvent.setOperationType(OperationType.valueOf(adminEventEntity.getOperationType()));
+ adminEvent.setResourcePath(adminEventEntity.getResourcePath());
+ adminEvent.setError(adminEventEntity.getError());
+
+ if(adminEventEntity.getRepresentation() != null) {
+ adminEvent.setRepresentation(adminEventEntity.getRepresentation());
+ }
+ return adminEvent;
+ }
+
+ private static void setAuthDetails(AdminEventEntity adminEventEntity, AuthDetails authDetails) {
+ adminEventEntity.setAuthRealmId(authDetails.getRealmId());
+ adminEventEntity.setAuthClientId(authDetails.getClientId());
+ adminEventEntity.setAuthUserId(authDetails.getUserId());
+ adminEventEntity.setAuthIpAddress(authDetails.getIpAddress());
+ }
+
+ private static void setAuthDetails(AdminEvent adminEvent, AdminEventEntity adminEventEntity) {
+ AuthDetails authDetails = new AuthDetails();
+ authDetails.setRealmId(adminEventEntity.getAuthRealmId());
+ authDetails.setClientId(adminEventEntity.getAuthClientId());
+ authDetails.setUserId(adminEventEntity.getAuthUserId());
+ authDetails.setIpAddress(adminEventEntity.getAuthIpAddress());
+ adminEvent.setAuthDetails(authDetails);
}
}
diff --git a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoAdminEventQuery.java b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoAdminEventQuery.java
new file mode 100644
index 0000000..4cc366c
--- /dev/null
+++ b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoAdminEventQuery.java
@@ -0,0 +1,126 @@
+package org.keycloak.events.mongo;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.OperationType;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.BasicDBObjectBuilder;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+
+public class MongoAdminEventQuery implements AdminEventQuery{
+
+ private Integer firstResult;
+ private Integer maxResults;
+ private DBCollection audit;
+ private final BasicDBObject query;
+
+ public MongoAdminEventQuery(DBCollection audit) {
+ this.audit = audit;
+ query = new BasicDBObject();
+ }
+
+ @Override
+ public AdminEventQuery operation(OperationType... operations) {
+ List<String> operationStrings = new LinkedList<String>();
+ for (OperationType e : operations) {
+ operationStrings.add(e.toString());
+ }
+ query.put("operationType", new BasicDBObject("$in", operationStrings));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authRealm(String realmId) {
+ query.put("realmId", realmId);
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authClient(String clientId) {
+ query.put("clientId", clientId);
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authUser(String userId) {
+ query.put("userId", userId);
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authIpAddress(String ipAddress) {
+ query.put("ipAddress", ipAddress);
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery resourcePath(String resourcePath) {
+ query.put("resourcePath", Pattern.compile(resourcePath));
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery fromTime(String fromTime) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ Long from = null;
+ try {
+ from = df.parse(fromTime).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ query.put("time", BasicDBObjectBuilder.start("$gte", from).get());
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery toTime(String toTime) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ Long to = null;
+ try {
+ to = df.parse(toTime).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ query.put("time", BasicDBObjectBuilder.start("$lte", to).get());
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery firstResult(int firstResult) {
+ this.firstResult = firstResult;
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery maxResults(int maxResults) {
+ this.maxResults = maxResults;
+ return this;
+ }
+
+ @Override
+ public List<AdminEvent> getResultList() {
+ DBCursor cur = audit.find(query).sort(new BasicDBObject("time", -1));
+ if (firstResult != null) {
+ cur.skip(firstResult);
+ }
+ if (maxResults != null) {
+ cur.limit(maxResults);
+ }
+
+ List<AdminEvent> events = new LinkedList<AdminEvent>();
+ while (cur.hasNext()) {
+ events.add(MongoEventStoreProvider.convertAdminEvent((BasicDBObject) cur.next()));
+ }
+
+ return events;
+ }
+
+}
diff --git a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventQuery.java b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventQuery.java
index c2569cc..75165c7 100755
--- a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventQuery.java
+++ b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventQuery.java
@@ -118,7 +118,7 @@ public class MongoEventQuery implements EventQuery {
List<Event> events = new LinkedList<Event>();
while (cur.hasNext()) {
- events.add(MongoEventStoreProvider.convert((BasicDBObject) cur.next()));
+ events.add(MongoEventStoreProvider.convertEvent((BasicDBObject) cur.next()));
}
return events;
diff --git a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProvider.java b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProvider.java
index 65b6573..4b0dd2c 100755
--- a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProvider.java
+++ b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProvider.java
@@ -3,6 +3,11 @@ package org.keycloak.events.mongo;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
+
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.AuthDetails;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
@@ -15,11 +20,13 @@ import java.util.Map;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class MongoEventStoreProvider implements EventStoreProvider {
-
+
private DBCollection events;
+ private DBCollection adminEvents;
- public MongoEventStoreProvider(DBCollection events) {
+ public MongoEventStoreProvider(DBCollection events, DBCollection adminEvents) {
this.events = events;
+ this.adminEvents = adminEvents;
}
@Override
@@ -47,27 +54,55 @@ public class MongoEventStoreProvider implements EventStoreProvider {
@Override
public void onEvent(Event event) {
- events.insert(convert(event));
+ events.insert(convertEvent(event));
+ }
+
+ @Override
+ public AdminEventQuery createAdminQuery() {
+ return new MongoAdminEventQuery(adminEvents);
+ }
+
+ @Override
+ public void clearAdmin() {
+ adminEvents.remove(new BasicDBObject());
+ }
+
+ @Override
+ public void clearAdmin(String realmId) {
+ adminEvents.remove(new BasicDBObject("realmId", realmId));
+ }
+
+ @Override
+ public void clearAdmin(String realmId, long olderThan) {
+ BasicDBObject q = new BasicDBObject();
+ q.put("realmId", realmId);
+ q.put("time", new BasicDBObject("$lt", olderThan));
+ adminEvents.remove(q);
+ }
+
+ @Override
+ public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
+ adminEvents.insert(convertAdminEvent(adminEvent, includeRepresentation));
}
@Override
public void close() {
}
- static DBObject convert(Event o) {
+ static DBObject convertEvent(Event event) {
BasicDBObject e = new BasicDBObject();
- e.put("time", o.getTime());
- e.put("type", o.getType().toString());
- e.put("realmId", o.getRealmId());
- e.put("clientId", o.getClientId());
- e.put("userId", o.getUserId());
- e.put("sessionId", o.getSessionId());
- e.put("ipAddress", o.getIpAddress());
- e.put("error", o.getError());
+ e.put("time", event.getTime());
+ e.put("type", event.getType().toString());
+ e.put("realmId", event.getRealmId());
+ e.put("clientId", event.getClientId());
+ e.put("userId", event.getUserId());
+ e.put("sessionId", event.getSessionId());
+ e.put("ipAddress", event.getIpAddress());
+ e.put("error", event.getError());
BasicDBObject details = new BasicDBObject();
- if (o.getDetails() != null) {
- for (Map.Entry<String, String> entry : o.getDetails().entrySet()) {
+ if (event.getDetails() != null) {
+ for (Map.Entry<String, String> entry : event.getDetails().entrySet()) {
details.put(entry.getKey(), entry.getValue());
}
}
@@ -76,16 +111,16 @@ public class MongoEventStoreProvider implements EventStoreProvider {
return e;
}
- static Event convert(BasicDBObject o) {
- Event e = new Event();
- e.setTime(o.getLong("time"));
- e.setType(EventType.valueOf(o.getString("type")));
- e.setRealmId(o.getString("realmId"));
- e.setClientId(o.getString("clientId"));
- e.setUserId(o.getString("userId"));
- e.setSessionId(o.getString("sessionId"));
- e.setIpAddress(o.getString("ipAddress"));
- e.setError(o.getString("error"));
+ static Event convertEvent(BasicDBObject o) {
+ Event event = new Event();
+ event.setTime(o.getLong("time"));
+ event.setType(EventType.valueOf(o.getString("type")));
+ event.setRealmId(o.getString("realmId"));
+ event.setClientId(o.getString("clientId"));
+ event.setUserId(o.getString("userId"));
+ event.setSessionId(o.getString("sessionId"));
+ event.setIpAddress(o.getString("ipAddress"));
+ event.setError(o.getString("error"));
BasicDBObject d = (BasicDBObject) o.get("details");
if (d != null) {
@@ -93,10 +128,55 @@ public class MongoEventStoreProvider implements EventStoreProvider {
for (Object k : d.keySet()) {
details.put((String) k, d.getString((String) k));
}
- e.setDetails(details);
+ event.setDetails(details);
+ }
+
+ return event;
+ }
+
+ private static DBObject convertAdminEvent(AdminEvent adminEvent, boolean includeRepresentation) {
+ BasicDBObject e = new BasicDBObject();
+ e.put("time", adminEvent.getTime());
+ e.put("operationType", adminEvent.getOperationType().toString());
+ setAuthDetails(e, adminEvent.getAuthDetails());
+ e.put("resourcePath", adminEvent.getResourcePath());
+ e.put("error", adminEvent.getError());
+
+ if(includeRepresentation) {
+ e.put("representation", adminEvent.getRepresentation());
}
return e;
}
+
+ static AdminEvent convertAdminEvent(BasicDBObject o) {
+ AdminEvent adminEvent = new AdminEvent();
+ adminEvent.setTime(o.getLong("time"));
+ adminEvent.setOperationType(OperationType.valueOf(o.getString("operationType")));
+ setAuthDetails(adminEvent, o);
+ adminEvent.setResourcePath(o.getString("resourcePath"));
+ adminEvent.setError(o.getString("error"));
+
+ if(o.getString("representation") != null) {
+ adminEvent.setRepresentation(o.getString("representation"));
+ }
+ return adminEvent;
+ }
+
+ private static void setAuthDetails(BasicDBObject e, AuthDetails authDetails) {
+ e.put("realmId", authDetails.getRealmId());
+ e.put("clientId", authDetails.getClientId());
+ e.put("userId", authDetails.getUserId());
+ e.put("ipAddress", authDetails.getIpAddress());
+ }
+
+ private static void setAuthDetails(AdminEvent adminEvent, BasicDBObject o) {
+ AuthDetails authDetails = new AuthDetails();
+ authDetails.setRealmId(o.getString("realmId"));
+ authDetails.setClientId(o.getString("clientId"));
+ authDetails.setUserId(o.getString("userId"));
+ authDetails.setIpAddress(o.getString("ipAddress"));
+ adminEvent.setAuthDetails(authDetails);
+ }
}
diff --git a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProviderFactory.java b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProviderFactory.java
index db4adeb..cbf41ac 100755
--- a/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProviderFactory.java
+++ b/events/mongo/src/main/java/org/keycloak/events/mongo/MongoEventStoreProviderFactory.java
@@ -24,9 +24,12 @@ public class MongoEventStoreProviderFactory implements EventStoreProviderFactory
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
DBCollection collection = connection.getDB().getCollection("events");
+ DBCollection adminCollection = connection.getDB().getCollection("adminEvents");
+
collection.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
+ adminCollection.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
- return new MongoEventStoreProvider(collection);
+ return new MongoEventStoreProvider(collection, adminCollection);
}
@Override
diff --git a/events/syslog/src/main/java/org/keycloak/events/log/SysLoggingEventListenerProvider.java b/events/syslog/src/main/java/org/keycloak/events/log/SysLoggingEventListenerProvider.java
index 0536286..14188a8 100755
--- a/events/syslog/src/main/java/org/keycloak/events/log/SysLoggingEventListenerProvider.java
+++ b/events/syslog/src/main/java/org/keycloak/events/log/SysLoggingEventListenerProvider.java
@@ -1,5 +1,6 @@
package org.keycloak.events.log;
+import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.productivity.java.syslog4j.SyslogConstants;
@@ -59,6 +60,33 @@ public class SysLoggingEventListenerProvider implements EventListenerProvider {
}
@Override
+ public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
+ int level = adminEvent.getError() != null ? SyslogConstants.LEVEL_ERROR : SyslogConstants.LEVEL_INFO;
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("operationType=");
+ sb.append(adminEvent.getOperationType());
+ sb.append(", realmId=");
+ sb.append(adminEvent.getAuthDetails().getRealmId());
+ sb.append(", clientId=");
+ sb.append(adminEvent.getAuthDetails().getClientId());
+ sb.append(", userId=");
+ sb.append(adminEvent.getAuthDetails().getUserId());
+ sb.append(", ipAddress=");
+ sb.append(adminEvent.getAuthDetails().getIpAddress());
+ sb.append(", resourcePath=");
+ sb.append(adminEvent.getResourcePath());
+
+ if (adminEvent.getError() != null) {
+ sb.append(", error=");
+ sb.append(adminEvent.getError());
+ }
+
+ syslogger.log(level, sb.toString());
+ }
+
+ @Override
public void close() {
}
diff --git a/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProvider.java b/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProvider.java
index 8bd001f..d81288e 100755
--- a/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProvider.java
+++ b/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProvider.java
@@ -1,5 +1,7 @@
package org.keycloak.examples.providers.events;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
@@ -13,9 +15,11 @@ import java.util.Set;
public class SysoutEventListenerProvider implements EventListenerProvider {
private Set<EventType> excludedEvents;
+ private Set<OperationType> excludedAdminOperations;
- public SysoutEventListenerProvider(Set<EventType> excludedEvents) {
+ public SysoutEventListenerProvider(Set<EventType> excludedEvents, Set<OperationType> excludedAdminOpearations) {
this.excludedEvents = excludedEvents;
+ this.excludedAdminOperations = excludedAdminOpearations;
}
@Override
@@ -28,6 +32,16 @@ public class SysoutEventListenerProvider implements EventListenerProvider {
}
}
+ @Override
+ public void onEvent(AdminEvent event, boolean includeRepresentation) {
+ // Ignore excluded operations
+ if (excludedAdminOperations != null && excludedAdminOperations.contains(event.getOperationType())) {
+ return;
+ } else {
+ System.out.println("EVENT: " + toString(event));
+ }
+ }
+
private String toString(Event event) {
StringBuilder sb = new StringBuilder();
@@ -64,7 +78,31 @@ public class SysoutEventListenerProvider implements EventListenerProvider {
return sb.toString();
}
+
+ private String toString(AdminEvent adminEvent) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("operationType=");
+ sb.append(adminEvent.getOperationType());
+ sb.append(", realmId=");
+ sb.append(adminEvent.getAuthDetails().getRealmId());
+ sb.append(", clientId=");
+ sb.append(adminEvent.getAuthDetails().getClientId());
+ sb.append(", userId=");
+ sb.append(adminEvent.getAuthDetails().getUserId());
+ sb.append(", ipAddress=");
+ sb.append(adminEvent.getAuthDetails().getIpAddress());
+ sb.append(", resourcePath=");
+ sb.append(adminEvent.getResourcePath());
+
+ if (adminEvent.getError() != null) {
+ sb.append(", error=");
+ sb.append(adminEvent.getError());
+ }
+
+ return sb.toString();
+ }
+
@Override
public void close() {
}
diff --git a/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProviderFactory.java b/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProviderFactory.java
index 3f87d81..e7eb8d5 100755
--- a/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProviderFactory.java
+++ b/examples/providers/event-listener-sysout/src/main/java/org/keycloak/examples/providers/events/SysoutEventListenerProviderFactory.java
@@ -4,6 +4,7 @@ import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.events.EventType;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -16,10 +17,11 @@ import java.util.Set;
public class SysoutEventListenerProviderFactory implements EventListenerProviderFactory {
private Set<EventType> excludedEvents;
+ private Set<OperationType> excludedAdminOperations;
@Override
public EventListenerProvider create(KeycloakSession session) {
- return new SysoutEventListenerProvider(excludedEvents);
+ return new SysoutEventListenerProvider(excludedEvents, excludedAdminOperations);
}
@Override
@@ -31,6 +33,14 @@ public class SysoutEventListenerProviderFactory implements EventListenerProvider
excludedEvents.add(EventType.valueOf(e));
}
}
+
+ String[] excludesOperations = config.getArray("excludesOperations");
+ if (excludesOperations != null) {
+ excludedAdminOperations = new HashSet<>();
+ for (String e : excludesOperations) {
+ excludedAdminOperations.add(OperationType.valueOf(e));
+ }
+ }
}
@Override
diff --git a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemAdminEventQuery.java b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemAdminEventQuery.java
new file mode 100644
index 0000000..830b7b5
--- /dev/null
+++ b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemAdminEventQuery.java
@@ -0,0 +1,162 @@
+package org.keycloak.examples.providers.events;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.OperationType;
+
+/**
+ * @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
+ */
+public class MemAdminEventQuery implements AdminEventQuery {
+
+ private List<AdminEvent> adminEvents;
+
+ private int first;
+ private int max;
+
+ public MemAdminEventQuery(List<AdminEvent> events) {
+ this.adminEvents = events;
+ }
+
+ @Override
+ public AdminEventQuery operation(OperationType... operations) {
+ Iterator<AdminEvent> itr = this.adminEvents.iterator();
+ while (itr.hasNext()) {
+ AdminEvent next = itr.next();
+ boolean include = false;
+ for (OperationType e : operations) {
+ if (next.getOperationType().equals(e)) {
+ include = true;
+ break;
+ }
+ }
+ if (!include) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authRealm(String realmId) {
+ Iterator<AdminEvent> itr = adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (!itr.next().getAuthDetails().getRealmId().equals(realmId)) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authClient(String clientId) {
+ Iterator<AdminEvent> itr = adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (!itr.next().getAuthDetails().getClientId().equals(clientId)) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authUser(String userId) {
+ Iterator<AdminEvent> itr = adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (!itr.next().getAuthDetails().getUserId().equals(userId)) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery authIpAddress(String ipAddress) {
+ Iterator<AdminEvent> itr = adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (!itr.next().getAuthDetails().getIpAddress().equals(ipAddress)) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery resourcePath(String resourcePath) {
+ Iterator<AdminEvent> itr = this.adminEvents.iterator();
+ while (itr.hasNext()) {
+ if(!Pattern.compile(resourcePath).matcher(itr.next().getResourcePath()).find()) {
+ itr.remove();
+ }
+ }
+ return (AdminEventQuery) this;
+ }
+
+ @Override
+ public AdminEventQuery fromTime(String fromTime) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ Long from = null;
+ try {
+ from = df.parse(fromTime).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ Iterator<AdminEvent> itr = this.adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (!(itr.next().getTime() >= from)) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery toTime(String toTime) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+ Long to = null;
+ try {
+ to = df.parse(toTime).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ Iterator<AdminEvent> itr = this.adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (!(itr.next().getTime() <= to)) {
+ itr.remove();
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery firstResult(int result) {
+ this.first = result;
+ return this;
+ }
+
+ @Override
+ public AdminEventQuery maxResults(int results) {
+ this.max = results;
+ return this;
+ }
+
+ @Override
+ public List<AdminEvent> getResultList() {
+ if (adminEvents.size() < first) {
+ return Collections.emptyList();
+ }
+ int end = first + max <= adminEvents.size() ? first + max : adminEvents.size();
+
+ return adminEvents.subList(first, end);
+ }
+
+}
diff --git a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java
index 592455c..7dd4606 100755
--- a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java
+++ b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProvider.java
@@ -1,5 +1,8 @@
package org.keycloak.examples.providers.events;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
@@ -16,10 +19,16 @@ import java.util.Set;
public class MemEventStoreProvider implements EventStoreProvider {
private final List<Event> events;
private final Set<EventType> excludedEvents;
+ private final List<AdminEvent> adminEvents;
+ private final Set<OperationType> excludedOperations;
- public MemEventStoreProvider(List<Event> events, Set<EventType> excludedEvents) {
+ public MemEventStoreProvider(List<Event> events, Set<EventType> excludedEvents,
+ List<AdminEvent> adminEvents, Set<OperationType> excludedOperations) {
this.events = events;
this.excludedEvents = excludedEvents;
+
+ this.adminEvents = adminEvents;
+ this.excludedOperations = excludedOperations;
}
@Override
@@ -65,6 +74,48 @@ public class MemEventStoreProvider implements EventStoreProvider {
}
@Override
+ public AdminEventQuery createAdminQuery() {
+ return new MemAdminEventQuery(new LinkedList<>(adminEvents));
+ }
+
+ @Override
+ public void clearAdmin() {
+
+ }
+
+ @Override
+ public void clearAdmin(String realmId) {
+ synchronized(adminEvents) {
+ Iterator<AdminEvent> itr = adminEvents.iterator();
+ while (itr.hasNext()) {
+ if (itr.next().getAuthDetails().getRealmId().equals(realmId)) {
+ itr.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void clearAdmin(String realmId, long olderThan) {
+ synchronized(adminEvents) {
+ Iterator<AdminEvent> itr = adminEvents.iterator();
+ while (itr.hasNext()) {
+ AdminEvent e = itr.next();
+ if (e.getAuthDetails().getRealmId().equals(realmId) && e.getTime() < olderThan) {
+ itr.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
+ if (excludedOperations == null || !excludedOperations.contains(adminEvent.getOperationType())) {
+ adminEvents.add(0, adminEvent);
+ }
+ }
+
+ @Override
public void close() {
}
diff --git a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProviderFactory.java b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProviderFactory.java
index 83fd80e..d09b1a3 100755
--- a/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProviderFactory.java
+++ b/examples/providers/event-store-mem/src/main/java/org/keycloak/examples/providers/events/MemEventStoreProviderFactory.java
@@ -5,6 +5,8 @@ import org.keycloak.events.Event;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.events.EventStoreProviderFactory;
import org.keycloak.events.EventType;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -20,12 +22,13 @@ import java.util.Set;
public class MemEventStoreProviderFactory implements EventStoreProviderFactory {
private List<Event> events;
-
private Set<EventType> excludedEvents;
+ private List<AdminEvent> adminEvents;
+ private Set<OperationType> excludedOperations;
@Override
public EventStoreProvider create(KeycloakSession session) {
- return new MemEventStoreProvider(events, excludedEvents);
+ return new MemEventStoreProvider(events, excludedEvents, adminEvents, excludedOperations);
}
@Override
@@ -39,6 +42,14 @@ public class MemEventStoreProviderFactory implements EventStoreProviderFactory {
excludedEvents.add(EventType.valueOf(e));
}
}
+
+ String excludesOperations = config.get("excludesOperations");
+ if (excludesOperations != null) {
+ excludedOperations = new HashSet<>();
+ for (String e : excludesOperations.split(",")) {
+ excludedOperations.add(OperationType.valueOf(e));
+ }
+ }
}
@Override
@@ -49,6 +60,8 @@ public class MemEventStoreProviderFactory implements EventStoreProviderFactory {
public void close() {
events = null;
excludedEvents = null;
+ adminEvents = null;
+ excludedOperations = null;
}
@Override
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 3ee1fbe..2a470e2 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -303,6 +303,18 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmEventsCtrl'
})
+ .when('/realms/:realm/admin-events', {
+ templateUrl : resourceUrl + '/partials/realm-events-admin.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ serverInfo : function(ServerInfoLoader) {
+ return ServerInfoLoader();
+ }
+ },
+ controller : 'RealmAdminEventsCtrl'
+ })
.when('/realms/:realm/events-settings', {
templateUrl : resourceUrl + '/partials/realm-events-config.html',
resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index f8ad7c3..3231640 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1180,7 +1180,7 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
}
});
-module.controller('RealmEventsConfigCtrl', function($scope, eventsConfig, RealmEventsConfig, RealmEvents, realm, serverInfo, $location, Notifications, TimeUnit, Dialog) {
+module.controller('RealmEventsConfigCtrl', function($scope, eventsConfig, RealmEventsConfig, RealmEvents, RealmAdminEvents, realm, serverInfo, $location, Notifications, TimeUnit, Dialog) {
$scope.realm = realm;
$scope.eventsConfig = eventsConfig;
@@ -1198,7 +1198,13 @@ module.controller('RealmEventsConfigCtrl', function($scope, eventsConfig, RealmE
$scope.eventSelectOptions = {
'multiple': true,
'simple_tags': true,
- 'tags': serverInfo.eventTypes
+ 'tags': serverInfo.enums['eventType']
+ };
+
+ $scope.adminEnabledEventOperationsOptions = {
+ 'multiple': true,
+ 'simple_tags': true,
+ 'tags': serverInfo.enums['operationType']
};
var oldCopy = angular.copy($scope.eventsConfig);
@@ -1238,6 +1244,14 @@ module.controller('RealmEventsConfigCtrl', function($scope, eventsConfig, RealmE
});
});
};
+
+ $scope.clearAdminEvents = function() {
+ Dialog.confirmDelete($scope.realm.realm, 'admin-events', function() {
+ RealmAdminEvents.remove({ id : $scope.realm.realm }, function() {
+ Notifications.success("The admin events has been cleared.");
+ });
+ });
+ };
});
module.controller('RealmEventsCtrl', function($scope, RealmEvents, realm, serverInfo) {
@@ -1247,7 +1261,7 @@ module.controller('RealmEventsCtrl', function($scope, RealmEvents, realm, server
$scope.eventSelectOptions = {
'multiple': true,
'simple_tags': true,
- 'tags': serverInfo.eventTypes
+ 'tags': serverInfo.enums['eventType']
};
$scope.query = {
@@ -1308,6 +1322,106 @@ module.controller('RealmEventsCtrl', function($scope, RealmEvents, realm, server
$scope.update();
});
+module.controller('RealmAdminEventsCtrl', function($scope, RealmAdminEvents, realm, serverInfo, $modal, $filter) {
+ $scope.realm = realm;
+ $scope.page = 0;
+
+ $scope.query = {
+ id : realm.realm,
+ max : 5,
+ first : 0
+ }
+ $scope.query.authRealm = 'master';
+
+ $scope.adminEnabledEventOperationsOptions = {
+ 'multiple': true,
+ 'simple_tags': true,
+ 'tags': serverInfo.enums['operationType']
+ };
+
+ $scope.update = function() {
+ $scope.query.first = 0;
+ for (var i in $scope.query) {
+ if ($scope.query[i] === '') {
+ delete $scope.query[i];
+ }
+ }
+ $scope.events = RealmAdminEvents.query($scope.query);
+ }
+
+ $scope.reset = function() {
+ $scope.query.first = 0;
+ $scope.query.max = 5;
+ $scope.query.operationTypes = '';
+ $scope.query.resourcePath = '';
+ $scope.query.authRealm = 'master';
+ $scope.query.authClient = '';
+ $scope.query.authUser = '';
+ $scope.query.authIpAddress = '';
+ $scope.query.dateFrom = '';
+ $scope.query.dateTo = '';
+
+ $scope.update();
+ }
+
+ $scope.queryUpdate = function() {
+ for (var i in $scope.query) {
+ if ($scope.query[i] === '') {
+ delete $scope.query[i];
+ }
+ }
+ $scope.events = RealmAdminEvents.query($scope.query);
+ }
+
+ $scope.firstPage = function() {
+ $scope.query.first = 0;
+ $scope.queryUpdate();
+ }
+
+ $scope.previousPage = function() {
+ $scope.query.first -= parseInt($scope.query.max);
+ if ($scope.query.first < 0) {
+ $scope.query.first = 0;
+ }
+ $scope.queryUpdate();
+ }
+
+ $scope.nextPage = function() {
+ $scope.query.first += parseInt($scope.query.max);
+ $scope.queryUpdate();
+ }
+
+ $scope.update();
+
+ $scope.viewRepresentation = function(event) {
+ $modal.open({
+ templateUrl: resourceUrl + '/partials/modal/realm-events-admin-representation.html',
+ controller: 'RealmAdminEventsModalCtrl',
+ resolve: {
+ event: function () {
+ return event;
+ }
+ }
+ })
+ }
+
+ $scope.viewAuth = function(event) {
+ $modal.open({
+ templateUrl: resourceUrl + '/partials/modal/realm-events-admin-auth.html',
+ controller: 'RealmAdminEventsModalCtrl',
+ resolve: {
+ event: function () {
+ return event;
+ }
+ }
+ })
+ }
+});
+
+module.controller('RealmAdminEventsModalCtrl', function($scope, $filter, event) {
+ $scope.event = event;
+});
+
module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, TimeUnit) {
console.log('RealmBruteForceCtrl');
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index 7708821..1b43027 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -180,6 +180,12 @@ module.factory('RealmEvents', function($resource) {
});
});
+module.factory('RealmAdminEvents', function($resource) {
+ return $resource(authUrl + '/admin/realms/:id/admin-events', {
+ id : '@realm'
+ });
+});
+
module.factory('RealmLDAPConnectionTester', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/testLDAPConnection');
});
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-auth.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-auth.html
new file mode 100644
index 0000000..8f765f0
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-auth.html
@@ -0,0 +1,8 @@
+<div style="padding: 20px 20px 0 20px">
+<table class="table table-striped table-bordered">
+ <tr><td width="100px">Realm</td><td>{{event.authDetails.realmId}}</td></tr>
+ <tr><td width="100px">Client</td><td>{{event.authDetails.clientId}}</td></tr>
+ <tr><td width="100px">User</td><td>{{event.authDetails.userId}}</td></tr>
+ <tr><td width="100px">IP Address</td><td>{{event.authDetails.ipAddress}}</td></tr>
+</table>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-representation.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-representation.html
new file mode 100644
index 0000000..837a164
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/realm-events-admin-representation.html
@@ -0,0 +1,3 @@
+<div style="padding: 20px 20px 10px 20px">
+ <pre ng-bind = "{{event.representation}} | json"></pre>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events.html
index 7c45014..8df1629 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events.html
@@ -5,7 +5,8 @@
</h1>
<ul class="nav nav-tabs">
- <li data-ng-class="(path[2] == 'events') && 'active'"><a href="#/realms/{{realm.realm}}/events">View</a></li>
+ <li data-ng-class="(path[2] == 'events') && 'active'"><a href="#/realms/{{realm.realm}}/events">Login Events</a></li>
+ <li data-ng-class="(path[2] == 'admin-events') && 'active'"><a href="#/realms/{{realm.realm}}/admin-events">Admin Events</a></li>
<li data-ng-class="(path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events-settings">Config</a></li>
</ul>
@@ -121,4 +122,4 @@
</table>
</div>
-<kc-menu></kc-menu>
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html
new file mode 100755
index 0000000..7168aab
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html
@@ -0,0 +1,128 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <h1>
+ <span><strong>Admin Events</strong> {{realm.realm|capitalize}}</span>
+ <kc-tooltip>Displays saved admin events for the realm. Events are related to admin account, for example a realm creation. To enable persisted events go to config.</kc-tooltip>
+ </h1>
+
+ <ul class="nav nav-tabs">
+ <li data-ng-class="(path[2] == 'events') && 'active'"><a href="#/realms/{{realm.realm}}/events">Login Events</a></li>
+ <li data-ng-class="(path[2] == 'admin-events') && 'active'"><a href="#/realms/{{realm.realm}}/admin-events">Admin Events</a></li>
+ <li data-ng-class="(path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events-settings">Config</a></li>
+ </ul>
+ <h2></h2>
+
+ <div id="content">
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th class="kc-table-actions" colspan="5">
+ <div class="pull-right">
+ <select data-ng-model="query.max" data-ng-click="update()" class="btn btn-default">
+ <option>5</option>
+ <option>10</option>
+ <option>50</option>
+ <option>100</option>
+ </select>
+ <button class="btn btn-default" data-ng-click="filter = !filter">
+ <span class="glyphicon glyphicon-plus" data-ng-show="!filter"></span>
+ <span class="glyphicon glyphicon-minus" data-ng-show="filter"></span>
+ Filter
+ </button>
+ <button class="btn btn-default btn-primary" data-ng-click="update()">Update</button>
+ <button class="btn btn-default btn-primary" data-ng-click="reset()">Reset</button>
+ </div>
+ <form class="form-horizontal" data-ng-show="filter">
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="adminEnabledEventOperations">Operation Types</label>
+ <div class="col-sm-5">
+ <input ui-select2="adminEnabledEventOperationsOptions" id="adminEnabledEventOperations" ng-model="query.operationTypes" data-placeholder="Select operations..."/>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="resource">Resource Path</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="text" id="resource" name="resource" data-ng-model="query.resourcePath">
+ </div>
+ <span tooltip-placement="right" tooltip="Filter by resource path. Supports wildcards '*' to match a single part of the path and '**' matches multiple parts. For example 'realms/*/clients/asbc' matches client with id asbc in any realm, while or 'realms/master/**' matches anything in the master realm." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="dateFrom">Date (From)</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="date" id="dateFrom" name="dateFrom" data-ng-model="query.dateFrom">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="dateTo">Date (To)</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="date" id="dateTo" name="dateTo" data-ng-model="query.dateTo">
+ </div>
+ </div>
+
+ <fieldset>
+ <legend><span class="text">Authentication Details</span></legend>
+
+ <div class="form-group" data-ng-show="'master' === realm.realm">
+ <label class="col-sm-2 control-label" for="realm">Realm</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="text" id="realm" name="realm" data-ng-model="query.authRealm">
+ </div>
+ <span tooltip-placement="right" tooltip="Filter by realm Id. This filter is supported only for master realm." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="client">Client</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="text" id="client" name="client" data-ng-model="query.authClient">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="user">User</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="text" id="user" name="user" data-ng-model="query.authUser">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="ipAddress">IP Address</label>
+ <div class="col-sm-4">
+ <input class="form-control" type="text" id="ipAddress" name="ipAddress" data-ng-model="query.authIpAddress">
+ </div>
+ </div>
+ </fieldset>
+
+ </form>
+ </th>
+ </tr>
+ <tr>
+ <th width="100px">Time</th>
+ <th width="180px">Operation Type</th>
+ <th width="180px">Resource Path</th>
+ <th>Details</th>
+ </tr>
+ </thead>
+ <tfoot>
+ <tr>
+ <td colspan="7">
+ <button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0"><i data-ng-class="query.first == 0 && 'text-muted'" class="fa fa-angle-double-left"></i></button>
+ <button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0"><i data-ng-class="query.first == 0 && 'text-muted'" class="fa fa-angle-left"></i></button>
+ <button data-ng-click="nextPage()" class="next" ng-disabled="events.length < query.max"><i data-ng-class="events.length < query.max && 'text-muted'" class="fa fa-angle-right"></i></button>
+ </td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <tr data-ng-repeat="event in events">
+ <td>{{event.time|date:'shortDate'}}<br>{{event.time|date:'mediumTime'}}</td>
+ <td data-ng-class="events-error">{{event.operationType}}</td>
+ <td>{{event.resourcePath}}</td>
+ <td>
+ <button type="button" class="btn btn-default btn-xs" data-ng-click="viewAuth(event)">
+ Auth
+ </button>
+ <button type="button" class="btn btn-default btn-xs" data-ng-click="viewRepresentation(event)" data-ng-show="event.representation">
+ Representation
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html
index 1b34385..7d05980 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html
@@ -2,69 +2,120 @@
<h1><strong>Events</strong> {{realm.realm|capitalize}}</span> Events</h1>
<ul class="nav nav-tabs">
- <li data-ng-class="(path[2] == 'events') && 'active'"><a href="#/realms/{{realm.realm}}/events">View</a></li>
+ <li data-ng-class="(path[2] == 'events') && 'active'"><a href="#/realms/{{realm.realm}}/events">Login Events</a></li>
+ <li data-ng-class="(path[2] == 'admin-events') && 'active'"><a href="#/realms/{{realm.realm}}/admin-events">Admin Events</a></li>
<li data-ng-class="(path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events-settings">Config</a></li>
</ul>
+ <div id="content">
+ <h2><span>{{realm.realm}}</span> Events Config</h2>
- <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageEvents">
- <fieldset class="border-top">
- <div class="form-group">
- <label class="col-md-2 control-label" for="enabled">Save Events</label>
- <div class="col-md-6">
- <input ng-model="eventsConfig.eventsEnabled" name="enabled" id="enabled" onoffswitch />
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageEvents">
+
+ <fieldset class="border-top">
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="eventsListeners" class="control-label">Event Listeners</label>
+
+ <div class="col-md-6">
+ <select ui-select2 ng-model="eventsConfig.eventsListeners" data-placeholder="Select an action..." multiple>
+ <option ng-repeat="listener in eventListeners" value="{{listener}}">{{listener}}</option>
+ </select>
+ </div>
+
+ <span tooltip-placement="right" tooltip="Configure what listeners receive events for the realm." class="fa fa-info-circle"></span>
</div>
- <kc-tooltip>If enabled events are saved to the database which makes events available to the admin and account management consoles.</kc-tooltip>
- </div>
+ </fieldset>
- <div class="form-group" data-ng-show="eventsConfig.eventsEnabled">
- <label class="col-md-2 control-label" for="enabledEventTypes" class="control-label">Saved Types</label>
+ <fieldset>
+ <legend><span class="text">Login Events Settings</span></legend>
- <div class="col-md-6">
- <input ui-select2="eventSelectOptions" id="enabledEventTypes" ng-model="eventsConfig.enabledEventTypes" data-placeholder="Select event types..."/>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="enabled">Save Events</label>
+ <div class="col-md-6">
+ <input ng-model="eventsConfig.eventsEnabled" name="enabled" id="enabled" onoffswitch />
+ </div>
+ <span tooltip-placement="right" tooltip="If enabled login events are saved to the database which makes events available to the admin and account management consoles." class="fa fa-info-circle"></span>
</div>
- <kc-tooltip>Configure what event types are saved. By default events related to login and users modifying their accounts are persisted.</kc-tooltip>
- </div>
+ <div class="form-group" data-ng-show="eventsConfig.eventsEnabled">
+ <label class="col-md-2 control-label" for="enabledEventTypes" class="control-label">Saved Types</label>
+
+ <div class="col-md-6">
+ <input ui-select2="eventSelectOptions" id="enabledEventTypes" ng-model="eventsConfig.enabledEventTypes" data-placeholder="Select event types..."/>
+ </div>
- <div class="form-group" data-ng-show="access.manageEvents && eventsConfig.eventsEnabled">
- <label class="col-md-2 control-label" for="password">Clear Events</label>
- <div class="col-md-6">
- <button class="btn btn-danger" type="submit" data-ng-click="clearEvents()" >Clear Events</button>
+ <span tooltip-placement="right" tooltip="Configure what event types are saved. By default events related to login and users modifying their accounts are persisted." class="fa fa-info-circle"></span>
</div>
- <kc-tooltip>Deletes all events in the database.</kc-tooltip>
- </div>
- <div class="form-group input-select" data-ng-show="eventsConfig.eventsEnabled">
- <label class="col-md-2 control-label" for="expiration">Expiration</label>
- <div class="col-md-6 form-inline">
- <input class="form-control" type="number" data-ng-model="eventsConfig.eventsExpiration" id="expiration" name="expiration" min="0"/>
- <select class="form-control" name="expirationUnit" data-ng-model="eventsConfig.expirationUnit" >
- <option>Minutes</option>
- <option>Hours</option>
- <option>Days</option>
- </select>
+
+ <div class="form-group" data-ng-show="access.manageEvents && eventsConfig.eventsEnabled">
+ <label class="col-md-2 control-label" for="password">Clear Events</label>
+ <div class="col-md-6">
+ <button class="btn btn-danger" type="submit" data-ng-click="clearEvents()" >Clear Events</button>
+ </div>
+ <span tooltip-placement="right" tooltip="Deletes all events in the database." class="fa fa-info-circle"></span>
</div>
- <div class="col-sm-1"></div>
- <kc-tooltip>Sets the expiration for events. Expired events are periodically deleted from the database.</kc-tooltip>
- </div>
+ <div class="form-group input-select" data-ng-show="eventsConfig.eventsEnabled">
+ <label class="col-md-2 control-label" for="expiration">Expiration</label>
+ <div class="col-md-6">
+ <input class="form-control" type="number" data-ng-model="eventsConfig.eventsExpiration" id="expiration" name="expiration" min="0"/>
+ </div>
+ <div class="col-md-2 select-kc">
+ <select name="expirationUnit" data-ng-model="eventsConfig.expirationUnit" >
+ <option>Minutes</option>
+ <option>Hours</option>
+ <option>Days</option>
+ </select>
+ <span tooltip-placement="right" tooltip="Sets the expiration for events. Expired events are periodically deleted from the database." class="fa fa-info-circle"></span>
+ </div>
+ </div>
+ </fieldset>
+
- <div class="form-group">
- <label class="col-md-2 control-label" for="eventsListeners" class="control-label">Listeners</label>
+ <fieldset>
+ <legend><span class="text">Admin Events Settings</span></legend>
- <div class="col-md-6">
- <select ui-select2 ng-model="eventsConfig.eventsListeners" data-placeholder="Select an action..." multiple>
- <option ng-repeat="listener in eventListeners" value="{{listener}}">{{listener}}</option>
- </select>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="adminEventsEnabled">Save Events</label>
+ <div class="col-md-6">
+ <input ng-model="eventsConfig.adminEventsEnabled" name="adminEventsEnabled" id="adminEventsEnabled" onoffswitch />
+ </div>
+
+ <span tooltip-placement="right" tooltip="If enabled admin events are saved to the database which makes events available to the admin console." class="fa fa-info-circle"></span>
</div>
- <kc-tooltip>Configure what listeners receive events for the realm.</kc-tooltip>
- </div>
- </fieldset>
+ <div class="form-group" data-ng-show="eventsConfig.adminEventsEnabled">
+ <label class="col-md-2 control-label" for="adminEnabledEventOperations" class="control-label">Saved Operations</label>
- <div class="pull-right form-actions" data-ng-show="access.manageEvents">
- <button data-kc-reset data-ng-show="changed">Clear changes</button>
- <button data-kc-save data-ng-show="changed">Save</button>
- </div>
- </form>
-</div>
+ <div class="col-md-6">
+ <input ui-select2="adminEnabledEventOperationsOptions" id="adminEnabledEventOperations" ng-model="eventsConfig.adminEnabledEventOperations" data-placeholder="Select operations..."/>
+ </div>
+
+ <span tooltip-placement="right" tooltip="Configure what operations are saved." class="fa fa-info-circle"></span>
+ </div>
+
+ <div class="form-group" data-ng-show="eventsConfig.adminEventsEnabled">
+ <label class="col-md-2 control-label" for="adminEventsDetailsEnabled">Include Representation</label>
+ <div class="col-md-6">
+ <input ng-model="eventsConfig.adminEventsDetailsEnabled" name="adminEventsDetailsEnabled" id="adminEventsDetailsEnabled" onoffswitch />
+ </div>
-<kc-menu></kc-menu>
+ <span tooltip-placement="right" tooltip="Include JSON representation for create and update requests." class="fa fa-info-circle"></span>
+ </div>
+
+ <div class="form-group" data-ng-show="access.manageEvents && eventsConfig.adminEventsEnabled">
+ <label class="col-md-2 control-label" for="password">Clear Admin Events</label>
+ <div class="col-md-6">
+ <button class="btn btn-danger" type="submit" data-ng-click="clearAdminEvents()" >Clear Admin Events</button>
+ </div>
+ <span tooltip-placement="right" tooltip="Deletes all admin events in the database." class="fa fa-info-circle"></span>
+ </div>
+
+ </fieldset>
+
+ <div class="pull-right form-actions" data-ng-show="access.manageEvents">
+ <button data-kc-reset data-ng-show="changed">Clear changes</button>
+ <button data-kc-save data-ng-show="changed">Save</button>
+ </div>
+ </form>
+ </div>
+</div>
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java
new file mode 100644
index 0000000..ca225fc
--- /dev/null
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/AdminEventBean.java
@@ -0,0 +1,37 @@
+package org.keycloak.email.freemarker.beans;
+
+import java.util.Date;
+
+import org.keycloak.events.admin.AdminEvent;
+
+/**
+ * @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
+ */
+public class AdminEventBean {
+
+ private AdminEvent adminEvent;
+
+ public AdminEventBean(AdminEvent adminEvent) {
+ this.adminEvent = adminEvent;
+ }
+
+ public Date getDate() {
+ return new Date(adminEvent.getTime());
+ }
+
+ public String getOperationType() {
+ return adminEvent.getOperationType().toString().toLowerCase();
+ }
+
+ public String getClient() {
+ return adminEvent.getAuthDetails().getClientId();
+ }
+
+ public String getIpAddress() {
+ return adminEvent.getAuthDetails().getIpAddress();
+ }
+
+ public String getResourcePath() {
+ return adminEvent.getResourcePath();
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 3c978f9..cbb4c6d 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -63,7 +63,11 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private long eventsExpiration;
private List<String> eventsListeners = new ArrayList<String>();
private List<String> enabledEventTypes = new ArrayList<String>();
-
+
+ protected boolean adminEventsEnabled;
+ protected List<String> adminEnabledEventOperations = new ArrayList<String>();;
+ protected boolean adminEventsDetailsEnabled;
+
private String masterAdminClient;
private boolean internationalizationEnabled;
@@ -391,6 +395,30 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.enabledEventTypes = enabledEventTypes;
}
+ public boolean isAdminEventsEnabled() {
+ return adminEventsEnabled;
+ }
+
+ public void setAdminEventsEnabled(boolean adminEventsEnabled) {
+ this.adminEventsEnabled = adminEventsEnabled;
+ }
+
+ public List<String> getAdminEnabledEventOperations() {
+ return adminEnabledEventOperations;
+ }
+
+ public void setAdminEnabledEventOperations(List<String> adminEnabledEventOperations) {
+ this.adminEnabledEventOperations = adminEnabledEventOperations;
+ }
+
+ public boolean isAdminEventsDetailsEnabled() {
+ return adminEventsDetailsEnabled;
+ }
+
+ public void setAdminEventsDetailsEnabled(boolean adminEventsDetailsEnabled) {
+ this.adminEventsDetailsEnabled = adminEventsDetailsEnabled;
+ }
+
public String getMasterAdminClient() {
return masterAdminClient;
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 8b901a2..3476374 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -232,7 +232,19 @@ public interface RealmModel extends RoleContainerModel {
Set<String> getEnabledEventTypes();
void setEnabledEventTypes(Set<String> enabledEventTypes);
+
+ boolean isAdminEventsEnabled();
+
+ void setAdminEventsEnabled(boolean enabled);
+
+ Set<String> getAdminEnabledEventOperations();
+ void setAdminEnabledEventOperations(Set<String> adminEnabledEventOperations);
+
+ boolean isAdminEventsDetailsEnabled();
+
+ void setAdminEventsDetailsEnabled(boolean enabled);
+
ClientModel getMasterAdminClient();
void setMasterAdminClient(ClientModel client);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 32f4e89..144c140 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -14,6 +14,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@@ -192,6 +193,14 @@ public class ModelToRepresentation {
rep.setEnabledEventTypes(new LinkedList<String>(realm.getEnabledEventTypes()));
}
+ rep.setAdminEventsEnabled(realm.isAdminEventsEnabled());
+
+ if(realm.getAdminEnabledEventOperations() != null) {
+ rep.setAdminEnabledEventOperations(new LinkedList<String>(realm.getAdminEnabledEventOperations()));
+ }
+
+ rep.setAdminEventsDetailsEnabled(realm.isAdminEventsDetailsEnabled());
+
return rep;
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 45221e5..70976a8 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -410,10 +410,16 @@ public class RepresentationToModel {
if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme());
if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme());
if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme());
+
if (rep.isEventsEnabled() != null) realm.setEventsEnabled(rep.isEventsEnabled());
if (rep.getEventsExpiration() != null) realm.setEventsExpiration(rep.getEventsExpiration());
if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
+
+ if (rep.isAdminEventsEnabled() != null) realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
+ if (rep.getAdminEnabledEventOperations() != null) realm.setAdminEnabledEventOperations(new HashSet<>(rep.getAdminEnabledEventOperations()));
+ if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
+
if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index cdd658a..6501981 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -959,6 +959,40 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public boolean isAdminEventsEnabled() {
+ return realm.isAdminEventsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsEnabled(boolean enabled) {
+ realm.setAdminEventsEnabled(enabled);
+ }
+
+ @Override
+ public Set<String> getAdminEnabledEventOperations() {
+ return new HashSet<String>(realm.getAdminEnabledEventOperations());
+ }
+
+ @Override
+ public void setAdminEnabledEventOperations(Set<String> adminEnabledEventOperations) {
+ if (adminEnabledEventOperations != null) {
+ realm.setAdminEnabledEventOperations(new ArrayList<String>(adminEnabledEventOperations));
+ } else {
+ realm.setAdminEnabledEventOperations(Collections.EMPTY_LIST);
+ }
+ }
+
+ @Override
+ public boolean isAdminEventsDetailsEnabled() {
+ return realm.isAdminEventsDetailsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsDetailsEnabled(boolean enabled) {
+ realm.setAdminEventsDetailsEnabled(enabled);
+ }
+
+ @Override
public ClientModel getMasterAdminClient() {
return this.masterAdminApp;
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index e490814..97f2667 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -79,6 +79,9 @@ public class CachedRealm {
private long eventsExpiration;
private Set<String> eventsListeners = new HashSet<String>();
private Set<String> enabledEventTypes = new HashSet<String>();
+ protected boolean adminEventsEnabled;
+ protected Set<String> adminEnabledEventOperations = new HashSet<String>();
+ protected boolean adminEventsDetailsEnabled;
private List<String> defaultRoles = new LinkedList<String>();
private Map<String, String> realmRoles = new HashMap<String, String>();
private Map<String, String> clients = new HashMap<String, String>();
@@ -153,6 +156,11 @@ public class CachedRealm {
eventsExpiration = model.getEventsExpiration();
eventsListeners.addAll(model.getEventsListeners());
enabledEventTypes.addAll(model.getEnabledEventTypes());
+
+ adminEventsEnabled = model.isAdminEventsEnabled();
+ adminEnabledEventOperations.addAll(model.getAdminEnabledEventOperations());
+ adminEventsDetailsEnabled = model.isAdminEventsDetailsEnabled();
+
defaultRoles.addAll(model.getDefaultRoles());
masterAdminClient = model.getMasterAdminClient().getId();
@@ -350,6 +358,18 @@ public class CachedRealm {
return enabledEventTypes;
}
+ public boolean isAdminEventsEnabled() {
+ return adminEventsEnabled;
+ }
+
+ public Set<String> getAdminEnabledEventOperations() {
+ return adminEnabledEventOperations;
+ }
+
+ public boolean isAdminEventsDetailsEnabled() {
+ return adminEventsDetailsEnabled;
+ }
+
public List<UserFederationProviderModel> getUserFederationProviders() {
return userFederationProviders;
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 68a65ac..dfa657f 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -751,6 +751,42 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public boolean isAdminEventsEnabled() {
+ if (updated != null) return updated.isAdminEventsEnabled();
+ return cached.isAdminEventsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsEnabled(boolean enabled) {
+ getDelegateForUpdate();
+ updated.setAdminEventsEnabled(enabled);
+ }
+
+ @Override
+ public Set<String> getAdminEnabledEventOperations() {
+ if (updated != null) return updated.getAdminEnabledEventOperations();
+ return cached.getAdminEnabledEventOperations();
+ }
+
+ @Override
+ public void setAdminEnabledEventOperations(Set<String> adminEnabledEventOperations) {
+ getDelegateForUpdate();
+ updated.setAdminEnabledEventOperations(adminEnabledEventOperations);
+ }
+
+ @Override
+ public boolean isAdminEventsDetailsEnabled() {
+ if (updated != null) return updated.isAdminEventsDetailsEnabled();
+ return cached.isAdminEventsDetailsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsDetailsEnabled(boolean enabled) {
+ getDelegateForUpdate();
+ updated.setAdminEventsDetailsEnabled(enabled);
+ }
+
+ @Override
public ClientModel getMasterAdminClient() {
return cacheSession.getRealm(Config.getAdminRealm()).getClientById(cached.getMasterAdminClient());
}
@@ -923,4 +959,5 @@ public class RealmAdapter implements RealmModel {
}
return null;
}
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index efdb4a8..9c69d1d 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -134,7 +134,18 @@ public class RealmEntity {
@Column(name="VALUE")
@CollectionTable(name="REALM_ENABLED_EVENT_TYPES", joinColumns={ @JoinColumn(name="REALM_ID") })
protected Set<String> enabledEventTypes = new HashSet<String>();
-
+
+ @Column(name="ADMIN_EVENTS_ENABLED")
+ protected boolean adminEventsEnabled;
+
+ @ElementCollection
+ @Column(name="VALUE")
+ @CollectionTable(name="REALM_ENABLED_ADMIN_EVENT_OPERATIONS", joinColumns={ @JoinColumn(name="REALM_ID") })
+ protected Set<String> adminEnabledEventOperations = new HashSet<String>();
+
+ @Column(name="ADMIN_EVENTS_DETAILS_ENABLED")
+ protected boolean adminEventsDetailsEnabled;
+
@OneToOne
@JoinColumn(name="MASTER_ADMIN_CLIENT")
protected ClientEntity masterAdminClient;
@@ -437,6 +448,30 @@ public class RealmEntity {
this.enabledEventTypes = enabledEventTypes;
}
+ public boolean isAdminEventsEnabled() {
+ return adminEventsEnabled;
+ }
+
+ public void setAdminEventsEnabled(boolean adminEventsEnabled) {
+ this.adminEventsEnabled = adminEventsEnabled;
+ }
+
+ public Set<String> getAdminEnabledEventOperations() {
+ return adminEnabledEventOperations;
+ }
+
+ public void setAdminEnabledEventOperations(Set<String> adminEnabledEventOperations) {
+ this.adminEnabledEventOperations = adminEnabledEventOperations;
+ }
+
+ public boolean isAdminEventsDetailsEnabled() {
+ return adminEventsDetailsEnabled;
+ }
+
+ public void setAdminEventsDetailsEnabled(boolean adminEventsDetailsEnabled) {
+ this.adminEventsDetailsEnabled = adminEventsDetailsEnabled;
+ }
+
public ClientEntity getMasterAdminClient() {
return masterAdminClient;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 8618ddb..bcad0bb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -22,6 +22,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
+
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -1065,8 +1066,42 @@ public class RealmAdapter implements RealmModel {
realm.setEnabledEventTypes(enabledEventTypes);
em.flush();
}
+
+ @Override
+ public boolean isAdminEventsEnabled() {
+ return realm.isAdminEventsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsEnabled(boolean enabled) {
+ realm.setAdminEventsEnabled(enabled);
+ em.flush();
+ }
+
+ @Override
+ public Set<String> getAdminEnabledEventOperations() {
+ return realm.getAdminEnabledEventOperations();
+ }
@Override
+ public void setAdminEnabledEventOperations(Set<String> adminEnabledEventOperations) {
+ realm.setAdminEnabledEventOperations(adminEnabledEventOperations);
+ em.flush();
+
+ }
+
+ @Override
+ public boolean isAdminEventsDetailsEnabled() {
+ return realm.isAdminEventsDetailsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsDetailsEnabled(boolean enabled) {
+ realm.setAdminEventsDetailsEnabled(enabled);
+ em.flush();
+ }
+
+ @Override
public ClientModel getMasterAdminClient() {
return new ClientAdapter(this, em, session, realm.getMasterAdminClient());
}
@@ -1327,4 +1362,5 @@ public class RealmAdapter implements RealmModel {
mapping.setConfig(config);
return mapping;
}
+
}
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index fb08290..c5b999e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
+
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.enums.SslRequired;
import org.keycloak.models.ClientModel;
@@ -985,8 +986,46 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
updateRealm();
}
+
+ @Override
+ public boolean isAdminEventsEnabled() {
+ return realm.isAdminEventsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsEnabled(boolean enabled) {
+ realm.setAdminEventsEnabled(enabled);
+ updateRealm();
+
+ }
+
+ @Override
+ public Set<String> getAdminEnabledEventOperations() {
+ return new HashSet<String>(realm.getAdminEnabledEventOperations());
+ }
+
+ @Override
+ public void setAdminEnabledEventOperations(Set<String> adminEnabledEventOperations) {
+ if (adminEnabledEventOperations != null) {
+ realm.setAdminEnabledEventOperations(new ArrayList<String>(adminEnabledEventOperations));
+ } else {
+ realm.setAdminEnabledEventOperations(Collections.EMPTY_LIST);
+ }
+ updateRealm();
+ }
@Override
+ public boolean isAdminEventsDetailsEnabled() {
+ return realm.isAdminEventsDetailsEnabled();
+ }
+
+ @Override
+ public void setAdminEventsDetailsEnabled(boolean enabled) {
+ realm.setAdminEventsDetailsEnabled(enabled);
+ updateRealm();
+ }
+
+ @Override
public ClientModel getMasterAdminClient() {
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
return appData != null ? new ClientAdapter(session, this, appData, invocationContext) : null;
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 88d9eb5..e3a3651 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -168,6 +168,12 @@ public class RealmManager {
if(rep.getEnabledEventTypes() != null) {
realm.setEnabledEventTypes(new HashSet<String>(rep.getEnabledEventTypes()));
}
+
+ realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
+ if(rep.getAdminEnabledEventOperations() != null) {
+ realm.setAdminEnabledEventOperations(new HashSet<String>(rep.getAdminEnabledEventOperations()));
+ }
+ realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
}
// Should be RealmManager moved to model/api instead of referencing methods this way?
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
index 34e8272..8b8253f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
@@ -8,6 +8,7 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.ClientConnection;
+import org.keycloak.events.AdminEventBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
@@ -29,6 +30,7 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+
import java.io.IOException;
/**
@@ -185,8 +187,11 @@ public class AdminRoot {
}
Cors.add(request).allowedOrigins(auth.getToken()).allowedMethods("GET", "PUT", "POST", "DELETE").auth().build(response);
-
- RealmsAdminResource adminResource = new RealmsAdminResource(auth, tokenManager);
+
+ AdminEventBuilder adminEvent = new AdminEventBuilder(auth.getRealm(), session, clientConnection);
+ adminEvent.user(auth.getUser()).client(auth.getClient());
+
+ RealmsAdminResource adminResource = new RealmsAdminResource(auth, tokenManager, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(adminResource);
return adminResource;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 16a0fbe..4e75efb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -6,6 +6,8 @@ import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.NotAcceptableException;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -21,6 +23,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -47,11 +50,12 @@ public class ClientAttributeCertificateResource {
private RealmAuth auth;
protected ClientModel client;
protected KeycloakSession session;
+ protected AdminEventBuilder adminEvent;
protected String attributePrefix;
protected String privateAttribute;
protected String certificateAttribute;
- public ClientAttributeCertificateResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, String attributePrefix) {
+ public ClientAttributeCertificateResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, String attributePrefix, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
this.client = client;
@@ -59,6 +63,7 @@ public class ClientAttributeCertificateResource {
this.attributePrefix = attributePrefix;
this.privateAttribute = attributePrefix + "." + PRIVATE_KEY;
this.certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
+ this.adminEvent = adminEvent;
}
public static class ClientKeyPairInfo {
@@ -94,6 +99,7 @@ public class ClientAttributeCertificateResource {
ClientKeyPairInfo info = new ClientKeyPairInfo();
info.setCertificate(client.getAttribute(certificateAttribute));
info.setPrivateKey(client.getAttribute(privateAttribute));
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return info;
}
@@ -134,6 +140,7 @@ public class ClientAttributeCertificateResource {
ClientKeyPairInfo info = new ClientKeyPairInfo();
info.setCertificate(client.getAttribute(certificateAttribute));
info.setPrivateKey(client.getAttribute(privateAttribute));
+ adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri().getPath()).representation(info).success();
return info;
}
@@ -190,7 +197,8 @@ public class ClientAttributeCertificateResource {
client.setAttribute(certificateAttribute, certPem);
info.setCertificate(certPem);
}
-
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(info).success();
return info;
}
@@ -316,6 +324,9 @@ public class ClientAttributeCertificateResource {
stream.flush();
stream.close();
byte[] rtn = stream.toByteArray();
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri().getPath()).success();
+
return rtn;
} catch (Exception e) {
throw new RuntimeException(e);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index f769101..3f0d0ea 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -5,6 +5,8 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@@ -40,6 +42,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -57,6 +60,7 @@ public class ClientResource {
protected static final Logger logger = Logger.getLogger(ClientResource.class);
protected RealmModel realm;
private RealmAuth auth;
+ private AdminEventBuilder adminEvent;
protected ClientModel client;
protected KeycloakSession session;
@@ -70,19 +74,21 @@ public class ClientResource {
return keycloak;
}
- public ClientResource(RealmModel realm, RealmAuth auth, ClientModel clientModel, KeycloakSession session) {
+ public ClientResource(RealmModel realm, RealmAuth auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
this.client = clientModel;
this.session = session;
+ this.adminEvent = adminEvent;
auth.init(RealmAuth.Resource.CLIENT);
}
@Path("protocol-mappers")
public ProtocolMappersResource getProtocolMappers() {
- ProtocolMappersResource mappers = new ProtocolMappersResource(client, auth);
+ ProtocolMappersResource mappers = new ProtocolMappersResource(client, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(mappers);
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return mappers;
}
@@ -98,6 +104,7 @@ public class ClientResource {
try {
RepresentationToModel.updateClient(rep, client);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
return Response.noContent().build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
@@ -115,7 +122,7 @@ public class ClientResource {
@Produces(MediaType.APPLICATION_JSON)
public ClientRepresentation getClient() {
auth.requireView();
-
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return ModelToRepresentation.toRepresentation(client);
}
@@ -126,7 +133,7 @@ public class ClientResource {
*/
@Path("certificates/{attr}")
public ClientAttributeCertificateResource getCertficateResource(@PathParam("attr") String attributePrefix) {
- return new ClientAttributeCertificateResource(realm, auth, client, session, attributePrefix);
+ return new ClientAttributeCertificateResource(realm, auth, client, session, attributePrefix, adminEvent);
}
@@ -145,6 +152,8 @@ public class ClientResource {
ClientManager clientManager = new ClientManager(new RealmManager(session));
Object rep = clientManager.toInstallationRepresentation(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
// TODO Temporary solution to pretty-print
return JsonSerialization.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep);
@@ -164,6 +173,9 @@ public class ClientResource {
auth.requireView();
ClientManager clientManager = new ClientManager(new RealmManager(session));
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
+
return clientManager.toJBossSubsystemConfig(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
}
@@ -176,6 +188,7 @@ public class ClientResource {
public void deleteClient() {
auth.requireManage();
new ClientManager(new RealmManager(session)).removeClient(realm, client);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
@@ -194,6 +207,7 @@ public class ClientResource {
logger.debug("regenerateSecret");
UserCredentialModel cred = KeycloakModelUtils.generateSecret(client);
CredentialRepresentation rep = ModelToRepresentation.toRepresentation(cred);
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getPath()).representation(rep).success();
return rep;
}
@@ -212,6 +226,7 @@ public class ClientResource {
logger.debug("getClientSecret");
UserCredentialModel model = UserCredentialModel.secret(client.getSecret());
if (model == null) throw new NotFoundException("Client does not have a secret");
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return ModelToRepresentation.toRepresentation(model);
}
@@ -222,12 +237,12 @@ public class ClientResource {
*/
@Path("scope-mappings")
public ScopeMappedResource getScopeMappedResource() {
- return new ScopeMappedResource(realm, auth, client, session);
+ return new ScopeMappedResource(realm, auth, client, session, adminEvent);
}
@Path("roles")
public RoleContainerResource getRoleContainerResource() {
- return new RoleContainerResource(realm, auth, client);
+ return new RoleContainerResource(realm, auth, client, adminEvent);
}
/**
@@ -243,7 +258,7 @@ public class ClientResource {
public Set<String> getAllowedOrigins()
{
auth.requireView();
-
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return client.getWebOrigins();
}
@@ -261,6 +276,7 @@ public class ClientResource {
auth.requireManage();
client.setWebOrigins(allowedOrigins);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(client).success();
}
/**
@@ -279,6 +295,7 @@ public class ClientResource {
for (String origin : allowedOrigins) {
client.removeWebOrigin(origin);
}
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -289,9 +306,11 @@ public class ClientResource {
@POST
public GlobalRequestResult pushRevocation() {
auth.requireManage();
- return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
+ return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
+
}
-
+
/**
* Number of user sessions associated with this client
*
@@ -309,6 +328,7 @@ public class ClientResource {
auth.requireView();
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("count", session.sessions().getActiveUserSessions(client.getRealm(), client));
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return map;
}
@@ -330,6 +350,7 @@ public class ClientResource {
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
sessions.add(rep);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return sessions;
}
@@ -341,7 +362,9 @@ public class ClientResource {
@POST
public GlobalRequestResult logoutAll() {
auth.requireManage();
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client);
+
}
/**
@@ -356,7 +379,9 @@ public class ClientResource {
if (user == null) {
throw new NotFoundException("User not found");
}
- new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
+ new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
+
}
/**
@@ -376,6 +401,7 @@ public class ClientResource {
}
if (logger.isDebugEnabled()) logger.debug("Register node: " + node);
client.registerNode(node, Time.currentTime());
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -394,8 +420,8 @@ public class ClientResource {
if (time == null) {
throw new NotFoundException("Client does not have a node " + node);
}
-
client.unregisterNode(node);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -408,9 +434,10 @@ public class ClientResource {
@NoCache
public GlobalRequestResult testNodesAvailable() {
auth.requireManage();
- logger.debug("Test availability of cluster nodes");
-
+ logger.debug("Test availability of cluster nodes");
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
return new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client);
+
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsByIdResource.java
index 46f2f87..bb9ae8d 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsByIdResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsByIdResource.java
@@ -1,5 +1,6 @@
package org.keycloak.services.resources.admin;
+import org.keycloak.events.AdminEventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
@@ -8,8 +9,8 @@ import org.keycloak.models.RealmModel;
* @version $Revision: 1 $
*/
public class ClientsByIdResource extends ClientsResource {
- public ClientsByIdResource(RealmModel realm, RealmAuth auth) {
- super(realm, auth);
+ public ClientsByIdResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
+ super(realm, auth, adminEvent);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index f6df036..2d19c51 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -4,6 +4,8 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@@ -23,6 +25,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.List;
@@ -36,13 +39,15 @@ public class ClientsResource {
protected static final Logger logger = Logger.getLogger(RealmAdminResource.class);
protected RealmModel realm;
private RealmAuth auth;
-
+ private AdminEventBuilder adminEvent;
+
@Context
protected KeycloakSession session;
- public ClientsResource(RealmModel realm, RealmAuth auth) {
+ public ClientsResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
+ this.adminEvent = adminEvent;
auth.init(RealmAuth.Resource.CLIENT);
}
@@ -72,7 +77,7 @@ public class ClientsResource {
rep.add(client);
}
}
-
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return rep;
}
@@ -90,6 +95,9 @@ public class ClientsResource {
try {
ClientModel clientModel = RepresentationToModel.createClient(session, realm, rep, true);
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(getClientPath(clientModel)).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(rep).success();
return Response.created(uriInfo.getAbsolutePathBuilder().path(getClientPath(clientModel)).build()).build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists");
@@ -112,8 +120,9 @@ public class ClientsResource {
if (clientModel == null) {
throw new NotFoundException("Could not find client: " + name);
}
- ClientResource clientResource = new ClientResource(realm, auth, clientModel, session);
+ ClientResource clientResource = new ClientResource(realm, auth, clientModel, session, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(clientResource);
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return clientResource;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index 539c410..d70cfd9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -6,6 +6,8 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderMapperModel;
@@ -13,7 +15,6 @@ import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation;
@@ -25,7 +26,6 @@ import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperTypeRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.ErrorResponse;
-import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.social.SocialIdentityProvider;
import javax.ws.rs.Consumes;
@@ -41,6 +41,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
@@ -58,14 +59,16 @@ public class IdentityProviderResource {
private final RealmModel realm;
private final KeycloakSession session;
private final IdentityProviderModel identityProviderModel;
-
+ private final AdminEventBuilder adminEvent;
+
@Context private UriInfo uriInfo;
- public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel) {
+ public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) {
this.realm = realm;
this.session = session;
this.identityProviderModel = identityProviderModel;
this.auth = auth;
+ this.adminEvent = adminEvent;
}
@GET
@@ -74,7 +77,9 @@ public class IdentityProviderResource {
public IdentityProviderRepresentation getIdentityProvider() {
this.auth.requireView();
IdentityProviderRepresentation rep = ModelToRepresentation.toRepresentation(this.identityProviderModel);
-
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
+
return rep;
}
@@ -84,7 +89,9 @@ public class IdentityProviderResource {
this.auth.requireManage();
this.realm.removeIdentityProviderByAlias(this.identityProviderModel.getAlias());
-
+
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
+
return Response.noContent().build();
}
@@ -108,7 +115,9 @@ public class IdentityProviderResource {
updateUsersAfterProviderAliasChange(this.session.users().getUsers(this.realm), oldProviderId, newProviderId);
}
-
+
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(providerRep).success();
+
return Response.noContent().build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Identity Provider " + providerRep.getAlias() + " already exists");
@@ -164,6 +173,7 @@ public class IdentityProviderResource {
try {
this.auth.requireView();
IdentityProviderFactory factory = getIdentityProviderFactory();
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
return factory.create(identityProviderModel).export(uriInfo, realm, format);
} catch (Exception e) {
return ErrorResponse.error("Could not export public broker configuration for identity provider [" + identityProviderModel.getProviderId() + "].", Response.Status.NOT_FOUND);
@@ -202,6 +212,7 @@ public class IdentityProviderResource {
}
}
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return types;
}
@@ -215,6 +226,7 @@ public class IdentityProviderResource {
for (IdentityProviderMapperModel model : realm.getIdentityProviderMappersByAlias(identityProviderModel.getAlias())) {
mappers.add(ModelToRepresentation.toRepresentation(model));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return mappers;
}
@@ -225,6 +237,9 @@ public class IdentityProviderResource {
auth.requireManage();
IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
model = realm.addIdentityProviderMapper(model);
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(model.getId()).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(mapper).success();
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
}
@@ -237,6 +252,7 @@ public class IdentityProviderResource {
auth.requireView();
IdentityProviderMapperModel model = realm.getIdentityProviderMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return ModelToRepresentation.toRepresentation(model);
}
@@ -250,6 +266,8 @@ public class IdentityProviderResource {
if (model == null) throw new NotFoundException("Model not found");
model = RepresentationToModel.toModel(rep);
realm.updateIdentityProviderMapper(model);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
+
}
@DELETE
@@ -260,6 +278,8 @@ public class IdentityProviderResource {
IdentityProviderMapperModel model = realm.getIdentityProviderMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
realm.removeIdentityProviderMapper(model);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index ecedae7..84ffe2f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -1,183 +1,198 @@
-package org.keycloak.services.resources.admin;
-
-import org.jboss.resteasy.annotations.cache.NoCache;
-import org.jboss.resteasy.plugins.providers.multipart.InputPart;
-import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
-import org.jboss.resteasy.spi.NotFoundException;
-import org.jboss.resteasy.spi.ResteasyProviderFactory;
-import org.keycloak.broker.provider.IdentityProvider;
-import org.keycloak.broker.provider.IdentityProviderFactory;
-import org.keycloak.connections.httpclient.HttpClientProvider;
-import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.models.utils.RepresentationToModel;
-import org.keycloak.provider.ProviderFactory;
-import org.keycloak.representations.idm.IdentityProviderRepresentation;
-import org.keycloak.services.ErrorResponse;
-import org.keycloak.social.SocialIdentityProvider;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
-
-/**
- * @author Pedro Igor
- */
-public class IdentityProvidersResource {
-
- private final RealmModel realm;
- private final KeycloakSession session;
- private RealmAuth auth;
-
- public IdentityProvidersResource(RealmModel realm, KeycloakSession session, RealmAuth auth) {
- this.realm = realm;
- this.session = session;
- this.auth = auth;
- this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER);
- }
-
- @Path("/providers/{provider_id}")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public Response getIdentityProviders(@PathParam("provider_id") String providerId) {
- this.auth.requireView();
- IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
-
- if (providerFactory != null) {
- return Response.ok(providerFactory).build();
- }
-
- return Response.status(BAD_REQUEST).build();
- }
-
- @POST
- @Path("import-config")
- @Consumes(MediaType.MULTIPART_FORM_DATA)
- @Produces(MediaType.APPLICATION_JSON)
- public Map<String, String> importFrom(@Context UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
- this.auth.requireManage();
- Map<String, List<InputPart>> formDataMap = input.getFormDataMap();
- String providerId = formDataMap.get("providerId").get(0).getBodyAsString();
- InputPart file = formDataMap.get("file").get(0);
- InputStream inputStream = file.getBody(InputStream.class, null);
- IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
- Map<String, String> config = providerFactory.parseConfig(inputStream);
- return config;
- }
-
- @POST
- @Path("import-config")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public Map<String, String> importFrom(@Context UriInfo uriInfo, Map<String, Object> data) throws IOException {
- this.auth.requireManage();
-
- String providerId = data.get("providerId").toString();
- String from = data.get("fromUrl").toString();
- InputStream inputStream = session.getProvider(HttpClientProvider.class).get(from);
- try {
- IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
- Map<String, String> config;
- config = providerFactory.parseConfig(inputStream);
- return config;
- } finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- }
- }
- }
-
- @GET
- @Path("instances")
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public List<IdentityProviderRepresentation> getIdentityProviders() {
- this.auth.requireView();
-
- List<IdentityProviderRepresentation> representations = new ArrayList<IdentityProviderRepresentation>();
-
- for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
- representations.add(ModelToRepresentation.toRepresentation(identityProviderModel));
- }
-
- return representations;
- }
-
- @POST
- @Path("instances")
- @Consumes(MediaType.APPLICATION_JSON)
- public Response create(@Context UriInfo uriInfo, IdentityProviderRepresentation representation) {
- this.auth.requireManage();
-
- try {
- IdentityProviderModel identityProvider = RepresentationToModel.toModel(representation);
- this.realm.addIdentityProvider(identityProvider);
-
- return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getProviderId()).build()).build();
- } catch (ModelDuplicateException e) {
- return ErrorResponse.exists("Identity Provider " + representation.getAlias() + " already exists");
- }
- }
-
- @Path("instances/{alias}")
- public IdentityProviderResource getIdentityProvider(@PathParam("alias") String alias) {
- this.auth.requireView();
- IdentityProviderModel identityProviderModel = null;
-
- for (IdentityProviderModel storedIdentityProvider : this.realm.getIdentityProviders()) {
- if (storedIdentityProvider.getAlias().equals(alias)
- || storedIdentityProvider.getInternalId().equals(alias)) {
- identityProviderModel = storedIdentityProvider;
- }
- }
-
- if (identityProviderModel == null) {
- throw new NotFoundException("Could not find identity provider: " + alias);
- }
-
- IdentityProviderResource identityProviderResource = new IdentityProviderResource(this.auth, realm, session, identityProviderModel);
- ResteasyProviderFactory.getInstance().injectProperties(identityProviderResource);
-
- return identityProviderResource;
- }
-
- private IdentityProviderFactory getProviderFactorytById(String providerId) {
- List<ProviderFactory> allProviders = getProviderFactories();
-
- for (ProviderFactory providerFactory : allProviders) {
- if (providerFactory.getId().equals(providerId)) {
- return (IdentityProviderFactory) providerFactory;
- }
- }
-
- return null;
- }
-
- private List<ProviderFactory> getProviderFactories() {
- List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
-
- allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
- allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
-
- return allProviders;
- }
+package org.keycloak.services.resources.admin;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.plugins.providers.multipart.InputPart;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.connections.httpclient.HttpClientProvider;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.social.SocialIdentityProvider;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+
+/**
+ * @author Pedro Igor
+ */
+public class IdentityProvidersResource {
+
+ private final RealmModel realm;
+ private final KeycloakSession session;
+ private RealmAuth auth;
+ private AdminEventBuilder adminEvent;
+
+ public IdentityProvidersResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) {
+ this.realm = realm;
+ this.session = session;
+ this.auth = auth;
+ this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER);
+ this.adminEvent = adminEvent;
+ }
+
+ @Path("/providers/{provider_id}")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getIdentityProviders(@PathParam("provider_id") String providerId) {
+ this.auth.requireView();
+ IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
+
+ if (providerFactory != null) {
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
+ return Response.ok(providerFactory).build();
+ }
+
+ return Response.status(BAD_REQUEST).build();
+ }
+
+ @POST
+ @Path("import-config")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, String> importFrom(@Context UriInfo uriInfo, MultipartFormDataInput input) throws IOException {
+ this.auth.requireManage();
+ Map<String, List<InputPart>> formDataMap = input.getFormDataMap();
+ String providerId = formDataMap.get("providerId").get(0).getBodyAsString();
+ InputPart file = formDataMap.get("file").get(0);
+ InputStream inputStream = file.getBody(InputStream.class, null);
+ IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
+ Map<String, String> config = providerFactory.parseConfig(inputStream);
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(config).success();
+
+ return config;
+ }
+
+ @POST
+ @Path("import-config")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, String> importFrom(@Context UriInfo uriInfo, Map<String, Object> data) throws IOException {
+ this.auth.requireManage();
+
+ String providerId = data.get("providerId").toString();
+ String from = data.get("fromUrl").toString();
+ InputStream inputStream = session.getProvider(HttpClientProvider.class).get(from);
+ try {
+ IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
+ Map<String, String> config;
+ config = providerFactory.parseConfig(inputStream);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(config).success();
+ return config;
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ @GET
+ @Path("instances")
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<IdentityProviderRepresentation> getIdentityProviders() {
+ this.auth.requireView();
+
+ List<IdentityProviderRepresentation> representations = new ArrayList<IdentityProviderRepresentation>();
+
+ for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
+ representations.add(ModelToRepresentation.toRepresentation(identityProviderModel));
+ }
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
+ return representations;
+ }
+
+ @POST
+ @Path("instances")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response create(@Context UriInfo uriInfo, IdentityProviderRepresentation representation) {
+ this.auth.requireManage();
+
+ try {
+ IdentityProviderModel identityProvider = RepresentationToModel.toModel(representation);
+ this.realm.addIdentityProvider(identityProvider);
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(representation.getProviderId()).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(representation).success();
+
+ return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getProviderId()).build()).build();
+ } catch (ModelDuplicateException e) {
+ return ErrorResponse.exists("Identity Provider " + representation.getAlias() + " already exists");
+ }
+ }
+
+ @Path("instances/{alias}")
+ public IdentityProviderResource getIdentityProvider(@PathParam("alias") String alias) {
+ this.auth.requireView();
+ IdentityProviderModel identityProviderModel = null;
+
+ for (IdentityProviderModel storedIdentityProvider : this.realm.getIdentityProviders()) {
+ if (storedIdentityProvider.getAlias().equals(alias)
+ || storedIdentityProvider.getInternalId().equals(alias)) {
+ identityProviderModel = storedIdentityProvider;
+ }
+ }
+
+ if (identityProviderModel == null) {
+ throw new NotFoundException("Could not find identity provider: " + alias);
+ }
+
+ IdentityProviderResource identityProviderResource = new IdentityProviderResource(this.auth, realm, session, identityProviderModel, adminEvent);
+ ResteasyProviderFactory.getInstance().injectProperties(identityProviderResource);
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
+ return identityProviderResource;
+ }
+
+ private IdentityProviderFactory getProviderFactorytById(String providerId) {
+ List<ProviderFactory> allProviders = getProviderFactories();
+
+ for (ProviderFactory providerFactory : allProviders) {
+ if (providerFactory.getId().equals(providerId)) {
+ return (IdentityProviderFactory) providerFactory;
+ }
+ }
+
+ return null;
+ }
+
+ private List<ProviderFactory> getProviderFactories() {
+ List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
+
+ allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
+ allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
+
+ return allProviders;
+ }
}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index 36428b8..a211087 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -3,6 +3,8 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
@@ -22,6 +24,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.util.LinkedList;
import java.util.List;
@@ -37,6 +40,8 @@ public class ProtocolMappersResource {
protected ClientModel client;
protected RealmAuth auth;
+
+ protected AdminEventBuilder adminEvent;
@Context
protected UriInfo uriInfo;
@@ -44,9 +49,10 @@ public class ProtocolMappersResource {
@Context
protected KeycloakSession session;
- public ProtocolMappersResource(ClientModel client, RealmAuth auth) {
+ public ProtocolMappersResource(ClientModel client, RealmAuth auth, AdminEventBuilder adminEvent) {
this.auth = auth;
this.client = client;
+ this.adminEvent = adminEvent;
auth.init(RealmAuth.Resource.USER);
}
@@ -67,6 +73,7 @@ public class ProtocolMappersResource {
for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
if (mapper.getProtocol().equals(protocol)) mappers.add(ModelToRepresentation.toRepresentation(mapper));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return mappers;
}
@@ -83,6 +90,9 @@ public class ProtocolMappersResource {
auth.requireManage();
ProtocolMapperModel model = RepresentationToModel.toModel(rep);
model = client.addProtocolMapper(model);
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(model.getId()).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(rep).success();
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
}
/**
@@ -95,10 +105,12 @@ public class ProtocolMappersResource {
@Consumes(MediaType.APPLICATION_JSON)
public void createMapper(List<ProtocolMapperRepresentation> reps) {
auth.requireManage();
+ ProtocolMapperModel model = null;
for (ProtocolMapperRepresentation rep : reps) {
- ProtocolMapperModel model = RepresentationToModel.toModel(rep);
+ model = RepresentationToModel.toModel(rep);
model = client.addProtocolMapper(model);
}
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getPath()).representation(reps).success();
}
@GET
@@ -111,6 +123,7 @@ public class ProtocolMappersResource {
for (ProtocolMapperModel mapper : client.getProtocolMappers()) {
mappers.add(ModelToRepresentation.toRepresentation(mapper));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return mappers;
}
@@ -122,6 +135,7 @@ public class ProtocolMappersResource {
auth.requireView();
ProtocolMapperModel model = client.getProtocolMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return ModelToRepresentation.toRepresentation(model);
}
@@ -135,6 +149,7 @@ public class ProtocolMappersResource {
if (model == null) throw new NotFoundException("Model not found");
model = RepresentationToModel.toModel(rep);
client.updateProtocolMapper(model);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
}
@DELETE
@@ -145,6 +160,8 @@ public class ProtocolMappersResource {
ProtocolMapperModel model = client.getProtocolMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
client.removeProtocolMapper(model);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
+
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index b0d171b..11b83a0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -5,10 +5,15 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.ClientConnection;
+import org.keycloak.Config;
+import org.keycloak.events.AdminEventBuilder;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.events.EventType;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AdminEventQuery;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.exportimport.ClientImporter;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -46,6 +51,7 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -63,6 +69,7 @@ public class RealmAdminResource {
protected RealmAuth auth;
protected RealmModel realm;
private TokenManager tokenManager;
+ private AdminEventBuilder adminEvent;
@Context
protected KeycloakSession session;
@@ -76,10 +83,11 @@ public class RealmAdminResource {
@Context
protected HttpHeaders headers;
- public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
+ public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager, AdminEventBuilder adminEvent) {
this.auth = auth;
this.realm = realm;
this.tokenManager = tokenManager;
+ this.adminEvent = adminEvent;
auth.init(RealmAuth.Resource.REALM);
}
@@ -102,7 +110,7 @@ public class RealmAdminResource {
*/
@Path("clients")
public ClientsResource getClients() {
- ClientsResource clientsResource = new ClientsResource(realm, auth);
+ ClientsResource clientsResource = new ClientsResource(realm, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(clientsResource);
return clientsResource;
}
@@ -114,7 +122,7 @@ public class RealmAdminResource {
*/
@Path("clients-by-id")
public ClientsByIdResource getClientsById() {
- ClientsByIdResource clientsResource = new ClientsByIdResource(realm, auth);
+ ClientsByIdResource clientsResource = new ClientsByIdResource(realm, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(clientsResource);
return clientsResource;
}
@@ -126,7 +134,7 @@ public class RealmAdminResource {
*/
@Path("roles")
public RoleContainerResource getRoleContainerResource() {
- return new RoleContainerResource(realm, auth, realm);
+ return new RoleContainerResource(realm, auth, realm, adminEvent);
}
/**
@@ -148,12 +156,14 @@ public class RealmAdminResource {
CacheUserProvider cache = (CacheUserProvider)session.userStorage();
rep.setUserCacheEnabled(cache.isEnabled());
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return rep;
} else {
auth.requireAny();
RealmRepresentation rep = new RealmRepresentation();
rep.setRealm(realm.getName());
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return rep;
}
}
@@ -188,7 +198,8 @@ public class RealmAdminResource {
for (final UserFederationProviderModel fedProvider : federationProviders) {
usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
}
-
+
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
return Response.noContent().build();
} catch (PatternSyntaxException e) {
return ErrorResponse.exists("Specified regex pattern(s) is invalid.");
@@ -209,6 +220,8 @@ public class RealmAdminResource {
if (!new RealmManager(session).removeRealm(realm)) {
throw new NotFoundException("Realm doesn't exist");
+ } else {
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
}
@@ -219,7 +232,7 @@ public class RealmAdminResource {
*/
@Path("users")
public UsersResource users() {
- UsersResource users = new UsersResource(realm, auth, tokenManager);
+ UsersResource users = new UsersResource(realm, auth, tokenManager, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(users);
//resourceContext.initResource(users);
return users;
@@ -227,7 +240,7 @@ public class RealmAdminResource {
@Path("user-federation")
public UserFederationResource userFederation() {
- UserFederationResource fed = new UserFederationResource(realm, auth);
+ UserFederationResource fed = new UserFederationResource(realm, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(fed);
//resourceContext.initResource(fed);
return fed;
@@ -240,7 +253,7 @@ public class RealmAdminResource {
*/
@Path("roles-by-id")
public RoleByIdResource rolesById() {
- RoleByIdResource resource = new RoleByIdResource(realm, auth);
+ RoleByIdResource resource = new RoleByIdResource(realm, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(resource);
//resourceContext.initResource(resource);
return resource;
@@ -254,7 +267,8 @@ public class RealmAdminResource {
@POST
public GlobalRequestResult pushRevocation() {
auth.requireManage();
- return new ResourceAdminManager(session).pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
+ return new ResourceAdminManager(session).pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm);
}
/**
@@ -266,7 +280,8 @@ public class RealmAdminResource {
@POST
public GlobalRequestResult logoutAll() {
session.sessions().removeUserSessions(realm);
- return new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
+ return new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm);
}
/**
@@ -279,8 +294,10 @@ public class RealmAdminResource {
@DELETE
public void deleteSession(@PathParam("session") String sessionId) {
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
- if (userSession == null) throw new NotFoundException("Sesssion not found");
- AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
+ if (userSession == null) throw new NotFoundException("Sesssion not found");
+ AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
+
}
/**
@@ -302,6 +319,7 @@ public class RealmAdminResource {
if (size == 0) continue;
stats.put(client.getClientId(), size);
}
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(stats).success();
return stats;
}
@@ -327,6 +345,7 @@ public class RealmAdminResource {
map.put("active", size + "");
data.add(map);
}
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(data).success();
return data;
}
@@ -366,6 +385,8 @@ public class RealmAdminResource {
* @param client app or oauth client name
* @param user user id
* @param ipAddress
+ * @param dateTo
+ * @param dateFrom
* @param firstResult
* @param maxResults
* @return
@@ -419,6 +440,86 @@ public class RealmAdminResource {
return query.getResultList();
}
+
+ /**
+ * Query admin events. Returns all admin events, or will query based on URL query parameters listed here
+ *
+ * @param client app or oauth client name
+ * @param operationTypes operation type
+ * @param authUser user id
+ * @param authIpAddress
+ * @param resourcePath
+ * @param dateTo
+ * @param dateFrom
+ * @param resourcePath
+ * @param firstResult
+ * @param maxResults
+ * @return
+ */
+ @Path("admin-events")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<AdminEvent> getEvents(@QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
+ @QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
+ @QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
+ @QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
+ @QueryParam("max") Integer maxResults) {
+ auth.init(RealmAuth.Resource.EVENTS).requireView();
+
+ EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
+ AdminEventQuery query = null;
+
+ if(realm.getName().equals(Config.getAdminRealm())) {
+ query = eventStore.createAdminQuery();
+ if(authRealm != null) {
+ query.authRealm(authRealm);
+ }
+ } else {
+ query = eventStore.createAdminQuery().authRealm(realm.getId());
+ }
+
+ if (authClient != null) {
+ query.authClient(authClient);
+ }
+
+ if (authUser != null) {
+ query.authUser(authUser);
+ }
+
+ if (authIpAddress != null) {
+ query.authIpAddress(authIpAddress);
+ }
+
+ if (resourcePath != null) {
+ query.resourcePath(resourcePath);
+ }
+
+ List<String> operationTypes = uriInfo.getQueryParameters().get("operationTypes");
+ if (operationTypes != null) {
+ OperationType[] t = new OperationType[operationTypes.size()];
+ for (int i = 0; i < t.length; i++) {
+ t[i] = OperationType.valueOf(operationTypes.get(i));
+ }
+ query.operation(t);
+ }
+
+ if(dateFrom != null) {
+ query.fromTime(dateFrom);
+ }
+ if(dateTo != null) {
+ query.toTime(dateTo);
+ }
+
+ if (firstResult != null) {
+ query.firstResult(firstResult);
+ }
+ if (maxResults != null) {
+ query.maxResults(maxResults);
+ }
+
+ return query.getResultList();
+ }
/**
* Delete all events.
@@ -432,6 +533,19 @@ public class RealmAdminResource {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clear(realm.getId());
}
+
+ /**
+ * Delete all admin events.
+ *
+ */
+ @Path("admin-events")
+ @DELETE
+ public void clearAdminEvents() {
+ auth.init(RealmAuth.Resource.EVENTS).requireManage();
+
+ EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
+ eventStore.clearAdmin(realm.getId());
+ }
@Path("testLDAPConnection")
@GET
@@ -446,6 +560,6 @@ public class RealmAdminResource {
@Path("identity-provider")
public IdentityProvidersResource getIdentityProviderResource() {
- return new IdentityProvidersResource(realm, session, this.auth);
+ return new IdentityProvidersResource(realm, session, this.auth, adminEvent);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
index c9fea3d..e50c25c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java
@@ -6,6 +6,8 @@ import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -33,6 +35,7 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
@@ -49,16 +52,18 @@ public class RealmsAdminResource {
protected static final Logger logger = Logger.getLogger(RealmsAdminResource.class);
protected AdminAuth auth;
protected TokenManager tokenManager;
+ protected AdminEventBuilder adminEvent;
@Context
protected KeycloakSession session;
-
+
@Context
protected KeycloakApplication keycloak;
- public RealmsAdminResource(AdminAuth auth, TokenManager tokenManager) {
+ public RealmsAdminResource(AdminAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) {
this.auth = auth;
this.tokenManager = tokenManager;
+ this.adminEvent = adminEvent;
}
public static final CacheControl noCache = new CacheControl();
@@ -87,6 +92,7 @@ public class RealmsAdminResource {
ClientModel adminApp = auth.getRealm().getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm()));
addRealmRep(reps, auth.getRealm(), adminApp);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
logger.debug(("getRealms()"));
return reps;
}
@@ -128,6 +134,8 @@ public class RealmsAdminResource {
URI location = AdminRoot.realmsUrl(uriInfo).path(realm.getName()).build();
logger.debugv("imported realm success, sending back: {0}", location.toString());
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(location.toString()).representation(rep).success();
return Response.created(location).build();
} catch (ModelDuplicateException e) {
@@ -158,10 +166,11 @@ public class RealmsAdminResource {
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
List<InputPart> inputParts = uploadForm.get("file");
-
+ RealmRepresentation rep = null;
+
for (InputPart inputPart : inputParts) {
// inputPart.getBody doesn't work as content-type is wrong, and inputPart.setMediaType is not supported on AS7 (RestEasy 2.3.2.Final)
- RealmRepresentation rep = JsonSerialization.readValue(inputPart.getBodyAsString(), RealmRepresentation.class);
+ rep = JsonSerialization.readValue(inputPart.getBodyAsString(), RealmRepresentation.class);
RealmModel realm;
try {
realm = realmManager.importRealm(rep);
@@ -170,13 +179,15 @@ public class RealmsAdminResource {
}
grantPermissionsToRealmCreator(realm);
-
+
+ URI location = null;
if (inputParts.size() == 1) {
- URI location = AdminRoot.realmsUrl(uriInfo).path(realm.getName()).build();
+ location = AdminRoot.realmsUrl(uriInfo).path(realm.getName()).build();
+ adminEvent.operation(OperationType.CREATE).resourcePath(location.toString()).representation(rep).success();
return Response.created(location).build();
}
}
-
+
return Response.noContent().build();
}
@@ -219,7 +230,7 @@ public class RealmsAdminResource {
realmAuth = new RealmAuth(auth, realm.getClientByClientId(realmManager.getRealmAdminClientId(auth.getRealm())));
}
- RealmAdminResource adminResource = new RealmAdminResource(realmAuth, realm, tokenManager);
+ RealmAdminResource adminResource = new RealmAdminResource(realmAuth, realm, tokenManager, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(adminResource);
//resourceContext.initResource(adminResource);
return adminResource;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
index 67d8c12..b54d44f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleByIdResource.java
@@ -3,6 +3,8 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -20,6 +22,8 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
import java.util.List;
import java.util.Set;
@@ -33,15 +37,17 @@ public class RoleByIdResource extends RoleResource {
protected static final Logger logger = Logger.getLogger(RoleByIdResource.class);
private final RealmModel realm;
private final RealmAuth auth;
+ private AdminEventBuilder adminEvent;
@Context
protected KeycloakSession session;
- public RoleByIdResource(RealmModel realm, RealmAuth auth) {
+ public RoleByIdResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
super(realm);
this.realm = realm;
this.auth = auth;
+ this.adminEvent = adminEvent;
}
/**
@@ -57,6 +63,8 @@ public class RoleByIdResource extends RoleResource {
public RoleRepresentation getRole(final @PathParam("role-id") String id) {
RoleModel roleModel = getRoleModel(id);
auth.requireView();
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getRole(roleModel);
}
@@ -76,6 +84,8 @@ public class RoleByIdResource extends RoleResource {
r = RealmAuth.Resource.USER;
}
auth.init(r);
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return roleModel;
}
@@ -92,6 +102,7 @@ public class RoleByIdResource extends RoleResource {
RoleModel role = getRoleModel(id);
auth.requireManage();
deleteRole(role);
+ adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri().getPath()).success();
}
/**
@@ -107,6 +118,7 @@ public class RoleByIdResource extends RoleResource {
RoleModel role = getRoleModel(id);
auth.requireManage();
updateRole(rep, role);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri().getPath()).representation(rep).success();
}
/**
@@ -122,6 +134,8 @@ public class RoleByIdResource extends RoleResource {
RoleModel role = getRoleModel(id);
auth.requireManage();
addComposites(roles, role);
+ adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri().getPath()).representation(roles).success();
+
}
/**
@@ -139,6 +153,7 @@ public class RoleByIdResource extends RoleResource {
if (logger.isDebugEnabled()) logger.debug("*** getRoleComposites: '" + id + "'");
RoleModel role = getRoleModel(id);
auth.requireView();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getRoleComposites(role);
}
@@ -155,6 +170,7 @@ public class RoleByIdResource extends RoleResource {
public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-id") String id) {
RoleModel role = getRoleModel(id);
auth.requireView();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getRealmRoleComposites(role);
}
@@ -178,6 +194,7 @@ public class RoleByIdResource extends RoleResource {
throw new NotFoundException("Could not find client: " + appName);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getClientRoleComposites(app, role);
}
@@ -201,6 +218,7 @@ public class RoleByIdResource extends RoleResource {
throw new NotFoundException("Could not find client: " + appId);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getClientRoleComposites(app, role);
}
@@ -217,6 +235,7 @@ public class RoleByIdResource extends RoleResource {
RoleModel role = getRoleModel(id);
auth.requireManage();
deleteComposites(roles, role);
+ adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri().getPath()).success();
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index fa0064f..43a41f6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -2,7 +2,10 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
@@ -23,6 +26,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -35,12 +39,14 @@ public class RoleContainerResource extends RoleResource {
private final RealmModel realm;
private final RealmAuth auth;
protected RoleContainerModel roleContainer;
+ private AdminEventBuilder adminEvent;
- public RoleContainerResource(RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer) {
+ public RoleContainerResource(RealmModel realm, RealmAuth auth, RoleContainerModel roleContainer, AdminEventBuilder adminEvent) {
super(realm);
this.realm = realm;
this.auth = auth;
this.roleContainer = roleContainer;
+ this.adminEvent = adminEvent;
}
/**
@@ -51,7 +57,7 @@ public class RoleContainerResource extends RoleResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List<RoleRepresentation> getRoles() {
+ public List<RoleRepresentation> getRoles(@Context final UriInfo uriInfo) {
auth.requireAny();
Set<RoleModel> roleModels = roleContainer.getRoles();
@@ -59,6 +65,7 @@ public class RoleContainerResource extends RoleResource {
for (RoleModel roleModel : roleModels) {
roles.add(ModelToRepresentation.toRepresentation(roleModel));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return roles;
}
@@ -77,6 +84,11 @@ public class RoleContainerResource extends RoleResource {
try {
RoleModel role = roleContainer.addRole(rep.getName());
role.setDescription(rep.getDescription());
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(role.getName()).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(rep).success();
+
return Response.created(uriInfo.getAbsolutePathBuilder().path(role.getName()).build()).build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Role with name " + rep.getName() + " already exists");
@@ -93,7 +105,7 @@ public class RoleContainerResource extends RoleResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public RoleRepresentation getRole(final @PathParam("role-name") String roleName) {
+ public RoleRepresentation getRole(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
auth.requireView();
RoleModel roleModel = roleContainer.getRole(roleName);
@@ -101,6 +113,8 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role: " + roleName);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
+
return getRole(roleModel);
}
@@ -112,15 +126,18 @@ public class RoleContainerResource extends RoleResource {
@Path("{role-name}")
@DELETE
@NoCache
- public void deleteRole(final @PathParam("role-name") String roleName) {
+ public void deleteRole(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
auth.requireManage();
- RoleRepresentation rep = getRole(roleName);
+ RoleRepresentation rep = getRole(uriInfo, roleName);
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role: " + roleName);
}
deleteRole(role);
+
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
+
}
/**
@@ -133,7 +150,7 @@ public class RoleContainerResource extends RoleResource {
@Path("{role-name}")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
- public Response updateRole(final @PathParam("role-name") String roleName, final RoleRepresentation rep) {
+ public Response updateRole(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName, final RoleRepresentation rep) {
auth.requireManage();
RoleModel role = roleContainer.getRole(roleName);
@@ -142,6 +159,9 @@ public class RoleContainerResource extends RoleResource {
}
try {
updateRole(rep, role);
+
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
+
return Response.noContent().build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("Role with name " + rep.getName() + " already exists");
@@ -157,7 +177,7 @@ public class RoleContainerResource extends RoleResource {
@Path("{role-name}/composites")
@POST
@Consumes(MediaType.APPLICATION_JSON)
- public void addComposites(final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
+ public void addComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
auth.requireManage();
RoleModel role = roleContainer.getRole(roleName);
@@ -165,6 +185,8 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role: " + roleName);
}
addComposites(roles, role);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(roles).success();
+
}
/**
@@ -177,13 +199,14 @@ public class RoleContainerResource extends RoleResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public Set<RoleRepresentation> getRoleComposites(final @PathParam("role-name") String roleName) {
+ public Set<RoleRepresentation> getRoleComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
auth.requireManage();
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role: " + roleName);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return getRoleComposites(role);
}
@@ -197,13 +220,14 @@ public class RoleContainerResource extends RoleResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public Set<RoleRepresentation> getRealmRoleComposites(final @PathParam("role-name") String roleName) {
+ public Set<RoleRepresentation> getRealmRoleComposites(@Context final UriInfo uriInfo, final @PathParam("role-name") String roleName) {
auth.requireManage();
RoleModel role = roleContainer.getRole(roleName);
if (role == null) {
throw new NotFoundException("Could not find role: " + roleName);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return getRealmRoleComposites(role);
}
@@ -218,7 +242,8 @@ public class RoleContainerResource extends RoleResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public Set<RoleRepresentation> getClientRoleComposites(final @PathParam("role-name") String roleName,
+ public Set<RoleRepresentation> getClientRoleComposites(@Context final UriInfo uriInfo,
+ final @PathParam("role-name") String roleName,
final @PathParam("clientId") String clientId) {
auth.requireManage();
@@ -231,6 +256,7 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find client: " + clientId);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return getClientRoleComposites(app, role);
}
@@ -246,7 +272,8 @@ public class RoleContainerResource extends RoleResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public Set<RoleRepresentation> getClientByIdRoleComposites(final @PathParam("role-name") String roleName,
+ public Set<RoleRepresentation> getClientByIdRoleComposites(@Context final UriInfo uriInfo,
+ final @PathParam("role-name") String roleName,
final @PathParam("id") String id) {
auth.requireManage();
@@ -259,6 +286,7 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find client: " + id);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return getClientRoleComposites(client, role);
}
@@ -272,7 +300,9 @@ public class RoleContainerResource extends RoleResource {
@Path("{role-name}/composites")
@DELETE
@Consumes(MediaType.APPLICATION_JSON)
- public void deleteComposites(final @PathParam("role-name") String roleName, List<RoleRepresentation> roles) {
+ public void deleteComposites(@Context final UriInfo uriInfo,
+ final @PathParam("role-name") String roleName,
+ List<RoleRepresentation> roles) {
auth.requireManage();
RoleModel role = roleContainer.getRole(roleName);
@@ -280,6 +310,7 @@ public class RoleContainerResource extends RoleResource {
throw new NotFoundException("Could not find role: " + roleName);
}
deleteComposites(roles, role);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
index 01fe1d6..2825dac 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java
@@ -2,6 +2,8 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -15,7 +17,10 @@ import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -30,13 +35,15 @@ public class ScopeMappedClientResource {
protected ClientModel client;
protected KeycloakSession session;
protected ClientModel scopedClient;
-
- public ScopeMappedClientResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, ClientModel scopedClient) {
+ protected AdminEventBuilder adminEvent;
+
+ public ScopeMappedClientResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
this.client = client;
this.session = session;
this.scopedClient = scopedClient;
+ this.adminEvent = adminEvent;
}
/**
@@ -55,6 +62,7 @@ public class ScopeMappedClientResource {
for (RoleModel roleModel : mappings) {
mapRep.add(ModelToRepresentation.toRepresentation(roleModel));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return mapRep;
}
@@ -71,6 +79,7 @@ public class ScopeMappedClientResource {
auth.requireView();
Set<RoleModel> roles = scopedClient.getRoles();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return ScopeMappedResource.getAvailable(client, roles);
}
@@ -87,6 +96,7 @@ public class ScopeMappedClientResource {
auth.requireView();
Set<RoleModel> roles = scopedClient.getRoles();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return ScopeMappedResource.getComposite(client, roles);
}
@@ -107,6 +117,7 @@ public class ScopeMappedClientResource {
}
client.addScopeMapping(roleModel);
}
+ adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri().getPath()).representation(roles).success();
}
@@ -135,5 +146,6 @@ public class ScopeMappedClientResource {
client.deleteScopeMapping(roleModel);
}
}
+ adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri().getPath()).success();
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
index 8d4e005..16b8744 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java
@@ -2,6 +2,8 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -18,7 +20,10 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -36,12 +41,14 @@ public class ScopeMappedResource {
private RealmAuth auth;
protected ClientModel client;
protected KeycloakSession session;
+ protected AdminEventBuilder adminEvent;
- public ScopeMappedResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session) {
+ public ScopeMappedResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
this.client = client;
this.session = session;
+ this.adminEvent = adminEvent;
}
/**
@@ -84,6 +91,7 @@ public class ScopeMappedResource {
}
}
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return all;
}
@@ -104,6 +112,7 @@ public class ScopeMappedResource {
for (RoleModel roleModel : realmMappings) {
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return realmMappingsRep;
}
@@ -120,6 +129,7 @@ public class ScopeMappedResource {
auth.requireView();
Set<RoleModel> roles = realm.getRoles();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getAvailable(client, roles);
}
@@ -147,6 +157,7 @@ public class ScopeMappedResource {
auth.requireView();
Set<RoleModel> roles = realm.getRoles();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getComposite(client, roles);
}
@@ -176,7 +187,7 @@ public class ScopeMappedResource {
}
client.addScopeMapping(roleModel);
}
-
+ adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri().getPath()).representation(roles).success();
}
@@ -206,6 +217,8 @@ public class ScopeMappedResource {
client.deleteScopeMapping(roleModel);
}
}
+ adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri().getPath()).success();
+
}
@Path("clients/{clientId}")
@@ -215,8 +228,8 @@ public class ScopeMappedResource {
if (app == null) {
throw new NotFoundException("Role not found");
}
-
- return new ScopeMappedClientResource(realm, auth, client, session, app);
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
+ return new ScopeMappedClientResource(realm, auth, client, session, app, adminEvent);
}
@Path("clients-by-id/{id}")
@@ -226,7 +239,7 @@ public class ScopeMappedResource {
if (app == null) {
throw new NotFoundException("Client not found");
}
-
- return new ScopeMappedClientResource(realm, auth, client, session, app);
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
+ return new ScopeMappedClientResource(realm, auth, client, session, app, adminEvent);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java
index f535b19..4e0d8f9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ServerInfoAdminResource.java
@@ -5,6 +5,7 @@ import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.exportimport.ClientImporter;
import org.keycloak.exportimport.ClientImporterFactory;
import org.keycloak.freemarker.Theme;
@@ -39,6 +40,8 @@ import java.util.Set;
*/
public class ServerInfoAdminResource {
+ private static final Map<String, List<String>> ENUMS = createEnumsMap(EventType.class, OperationType.class);
+
@Context
private KeycloakSession session;
@@ -61,7 +64,7 @@ public class ServerInfoAdminResource {
setProviders(info);
setProtocolMapperTypes(info);
setBuiltinProtocolMappers(info);
- setEventTypes(info);
+ info.setEnums(ENUMS);
return info;
}
@@ -181,15 +184,6 @@ public class ServerInfoAdminResource {
}
}
- private void setEventTypes(ServerInfoRepresentation info) {
- List<String> eventTypes = new LinkedList<>();
- for (EventType t : EventType.values()) {
- eventTypes.add(t.name());
- }
- Collections.sort(eventTypes);
- info.setEventTypes(eventTypes);
- }
-
public static class ServerInfoRepresentation {
private String version;
@@ -209,7 +203,7 @@ public class ServerInfoAdminResource {
private Map<String, List<ProtocolMapperTypeRepresentation>> protocolMapperTypes;
private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
- private List<String> eventTypes;
+ private Map<String, List<String>> enums;
public ServerInfoRepresentation() {
}
@@ -262,13 +256,30 @@ public class ServerInfoAdminResource {
this.builtinProtocolMappers = builtinProtocolMappers;
}
- public List<String> getEventTypes() {
- return eventTypes;
+ public Map<String, List<String>> getEnums() {
+ return enums;
+ }
+
+ public void setEnums(Map<String, List<String>> enums) {
+ this.enums = enums;
}
+ }
+
+ private static Map<String, List<String>> createEnumsMap(Class... enums) {
+ Map<String, List<String>> m = new HashMap<>();
+ for (Class e : enums) {
+ String n = e.getSimpleName();
+ n = Character.toLowerCase(n.charAt(0)) + n.substring(1);
+
+ List<String> l = new LinkedList<>();
+ for (Object c : e.getEnumConstants()) {
+ l.add(c.toString());
+ }
+ Collections.sort(l);
- public void setEventTypes(List<String> eventTypes) {
- this.eventTypes = eventTypes;
+ m.put(n, l);
}
+ return m;
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java
index e838333..39e180a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserClientRoleMappingsResource.java
@@ -3,7 +3,10 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
@@ -16,7 +19,10 @@ import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -33,12 +39,18 @@ public class UserClientRoleMappingsResource {
protected RealmAuth auth;
protected UserModel user;
protected ClientModel client;
+ protected AdminEventBuilder adminEvent;
+
+ @Context
+ protected KeycloakSession session;
+
- public UserClientRoleMappingsResource(RealmModel realm, RealmAuth auth, UserModel user, ClientModel client) {
+ public UserClientRoleMappingsResource(RealmModel realm, RealmAuth auth, UserModel user, ClientModel client, AdminEventBuilder adminEvent) {
this.realm = realm;
this.auth = auth;
this.user = user;
this.client = client;
+ this.adminEvent = adminEvent;
}
/**
@@ -57,6 +69,7 @@ public class UserClientRoleMappingsResource {
for (RoleModel roleModel : mappings) {
mapRep.add(ModelToRepresentation.toRepresentation(roleModel));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return mapRep;
}
@@ -77,6 +90,7 @@ public class UserClientRoleMappingsResource {
for (RoleModel roleModel : roles) {
if (user.hasRole(roleModel)) mapRep.add(ModelToRepresentation.toRepresentation(roleModel));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return mapRep;
}
@@ -93,6 +107,7 @@ public class UserClientRoleMappingsResource {
auth.requireView();
Set<RoleModel> available = client.getRoles();
+ adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return getAvailableRoles(user, available);
}
@@ -127,6 +142,7 @@ public class UserClientRoleMappingsResource {
}
user.grantRole(roleModel);
}
+ adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri().getPath()).representation(roles).success();
}
@@ -159,5 +175,6 @@ public class UserClientRoleMappingsResource {
user.deleteRoleMapping(roleModel);
}
}
+ adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri().getPath()).success();
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
index 188cb30..a1d6851 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
@@ -4,6 +4,8 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.constants.KerberosConstants;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
@@ -31,6 +33,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+
import java.util.LinkedList;
import java.util.List;
@@ -46,6 +49,8 @@ public class UserFederationResource {
protected RealmModel realm;
protected RealmAuth auth;
+
+ protected AdminEventBuilder adminEvent;
@Context
protected UriInfo uriInfo;
@@ -53,10 +58,11 @@ public class UserFederationResource {
@Context
protected KeycloakSession session;
- public UserFederationResource(RealmModel realm, RealmAuth auth) {
+ public UserFederationResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) {
this.auth = auth;
this.realm = realm;
-
+ this.adminEvent = adminEvent;
+
auth.init(RealmAuth.Resource.USER);
}
@@ -78,6 +84,7 @@ public class UserFederationResource {
rep.setOptions(((UserFederationProviderFactory)factory).getConfigurationOptions());
providers.add(rep);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return providers;
}
@@ -99,6 +106,9 @@ public class UserFederationResource {
UserFederationProviderFactoryRepresentation rep = new UserFederationProviderFactoryRepresentation();
rep.setId(factory.getId());
rep.setOptions(((UserFederationProviderFactory)factory).getConfigurationOptions());
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
+
return rep;
}
throw new NotFoundException("Could not find provider");
@@ -123,6 +133,11 @@ public class UserFederationResource {
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
checkKerberosCredential(model);
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(model.getId()).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(rep).success();
+
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
}
@@ -146,6 +161,9 @@ public class UserFederationResource {
realm.updateUserFederationProvider(model);
new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
checkKerberosCredential(model);
+
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
+
}
/**
@@ -161,10 +179,10 @@ public class UserFederationResource {
auth.requireView();
for (UserFederationProviderModel model : realm.getUserFederationProviders()) {
if (model.getId().equals(id)) {
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return ModelToRepresentation.toRepresentation(model);
}
}
-
throw new NotFoundException("could not find provider");
}
@@ -182,6 +200,9 @@ public class UserFederationResource {
UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0);
realm.removeUserFederationProvider(model);
new UsersSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model);
+
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
+
}
@@ -201,6 +222,7 @@ public class UserFederationResource {
UserFederationProviderRepresentation rep = ModelToRepresentation.toRepresentation(model);
reps.add(rep);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return reps;
}
@@ -224,6 +246,7 @@ public class UserFederationResource {
} else if ("triggerChangedUsersSync".equals(action)) {
syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model);
}
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
return Response.noContent().build();
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index ee9dc90..291387e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -7,6 +7,8 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.ClientConnection;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
+import org.keycloak.events.AdminEventBuilder;
+import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
@@ -56,6 +58,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -78,6 +81,8 @@ public class UsersResource {
private RealmAuth auth;
+ private AdminEventBuilder adminEvent;
+
@Context
protected ClientConnection clientConnection;
@@ -90,9 +95,10 @@ public class UsersResource {
@Context
protected HttpHeaders headers;
- public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager) {
+ public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) {
this.auth = auth;
this.realm = realm;
+ this.adminEvent = adminEvent;
auth.init(RealmAuth.Resource.USER);
}
@@ -116,11 +122,11 @@ public class UsersResource {
throw new NotFoundException("User not found");
}
updateUserFromRep(user, rep);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo.getPath()).representation(rep).success();
if (session.getTransaction().isActive()) {
session.getTransaction().commit();
}
-
return Response.noContent().build();
} catch (ModelDuplicateException e) {
return ErrorResponse.exists("User exists with same username or email");
@@ -152,11 +158,15 @@ public class UsersResource {
try {
UserModel user = session.users().addUser(realm, rep.getUsername());
updateUserFromRep(user, rep);
-
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo.getAbsolutePathBuilder()
+ .path(user.getUsername()).build().toString().substring(uriInfo.getBaseUri().toString().length()))
+ .representation(rep).success();
+
if (session.getTransaction().isActive()) {
session.getTransaction().commit();
}
-
+
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getUsername()).build()).build();
} catch (ModelDuplicateException e) {
if (session.getTransaction().isActive()) {
@@ -217,6 +227,8 @@ public class UsersResource {
if (user == null) {
throw new NotFoundException("User not found");
}
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
UserRepresentation rep = ModelToRepresentation.toRepresentation(user);
@@ -256,6 +268,7 @@ public class UsersResource {
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(session);
reps.add(rep);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return reps;
}
@@ -287,6 +300,7 @@ public class UsersResource {
}
}
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return result;
}
@@ -305,7 +319,7 @@ public class UsersResource {
FederatedIdentityModel socialLink = new FederatedIdentityModel(provider, rep.getUserId(), rep.getUserName());
session.users().addFederatedIdentity(realm, user, socialLink);
-
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(rep).success();
return Response.noContent().build();
}
@@ -321,6 +335,7 @@ public class UsersResource {
if (!session.users().removeFederatedIdentity(realm, user, provider)) {
throw new NotFoundException("Link not found");
}
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -347,6 +362,7 @@ public class UsersResource {
UserConsentRepresentation rep = ModelToRepresentation.toRepresentation(consent);
result.add(rep);
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return result;
}
@@ -374,6 +390,7 @@ public class UsersResource {
} else {
throw new NotFoundException("Consent not found for user " + username + " and client " + clientId);
}
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -395,6 +412,7 @@ public class UsersResource {
for (UserSessionModel userSession : userSessions) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
}
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -416,6 +434,7 @@ public class UsersResource {
boolean removed = new UserManager(session).removeUser(realm, user);
if (removed) {
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
return Response.noContent().build();
} else {
return ErrorResponse.error("User couldn't be deleted", Response.Status.BAD_REQUEST);
@@ -524,6 +543,7 @@ public class UsersResource {
}
}
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return all;
}
@@ -550,6 +570,7 @@ public class UsersResource {
for (RoleModel roleModel : realmMappings) {
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return realmMappingsRep;
}
@@ -578,6 +599,7 @@ public class UsersResource {
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
}
}
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return realmMappingsRep;
}
@@ -600,6 +622,7 @@ public class UsersResource {
}
Set<RoleModel> available = realm.getRoles();
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
return UserClientRoleMappingsResource.getAvailableRoles(user, available);
}
@@ -628,7 +651,8 @@ public class UsersResource {
}
user.grantRole(roleModel);
}
-
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).representation(roles).success();
}
@@ -665,6 +689,8 @@ public class UsersResource {
user.deleteRoleMapping(roleModel);
}
}
+
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo.getPath()).success();
}
@Path("{username}/role-mappings/clients/{clientId}")
@@ -679,8 +705,8 @@ public class UsersResource {
if (client == null) {
throw new NotFoundException("Client not found");
}
-
- return new UserClientRoleMappingsResource(realm, auth, user, client);
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
+ return new UserClientRoleMappingsResource(realm, auth, user, client, adminEvent);
}
@Path("{username}/role-mappings/clients-by-id/{id}")
@@ -695,8 +721,9 @@ public class UsersResource {
if (client == null) {
throw new NotFoundException("Client not found");
}
-
- return new UserClientRoleMappingsResource(realm, auth, user, client);
+
+ adminEvent.operation(OperationType.VIEW).resourcePath(uriInfo.getPath()).success();
+ return new UserClientRoleMappingsResource(realm, auth, user, client, adminEvent);
}
/**
@@ -729,6 +756,8 @@ public class UsersResource {
throw new BadRequestException("Can't reset password as account is read only");
}
if (pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -748,6 +777,7 @@ public class UsersResource {
}
user.setTotp(false);
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
}
/**
@@ -823,6 +853,9 @@ public class UsersResource {
this.session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendPasswordReset(link, expiration);
//audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo.getPath()).success();
+
return Response.ok().build();
} catch (EmailException e) {
logger.error("Failed to send password reset email", e);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 13c181b..ce80527 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -8,6 +8,7 @@ import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.keycloak.Config;
+import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.Details;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
@@ -195,6 +196,12 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
@Override
public void close() {
}
+
+ @Override
+ public void onEvent(AdminEvent event, boolean includeRepresentation) {
+ // TODO Auto-generated method stub
+
+ }
};
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/events/AdminEventStoreProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/events/AdminEventStoreProviderTest.java
new file mode 100644
index 0000000..dacf7c5
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/events/AdminEventStoreProviderTest.java
@@ -0,0 +1,226 @@
+package org.keycloak.testsuite.events;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.events.EventStoreProvider;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AuthDetails;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.testsuite.rule.KeycloakRule;
+
+/**
+ * @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
+ */
+public class AdminEventStoreProviderTest {
+
+ @ClassRule
+ public static KeycloakRule kc = new KeycloakRule();
+
+ private KeycloakSession session;
+
+ private EventStoreProvider eventStore;
+
+ @Before
+ public void before() {
+ session = kc.startSession();
+ eventStore = session.getProvider(EventStoreProvider.class);
+ }
+
+ @After
+ public void after() {
+ eventStore.clearAdmin();
+ kc.stopSession(session, true);
+ }
+
+ @Test
+ public void save() {
+ eventStore.onEvent(create(OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ }
+
+ @Test
+ public void query() {
+ long oldest = System.currentTimeMillis() - 30000;
+ long newest = System.currentTimeMillis() + 30000;
+
+ eventStore.onEvent(create(OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(newest, OperationType.ACTION, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(newest, OperationType.ACTION, "realmId", "clientId", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(OperationType.VIEW, "realmId2", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(oldest, OperationType.VIEW, "realmId", "clientId2", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(OperationType.VIEW, "realmId", "clientId", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+
+ resetSession();
+
+ Assert.assertEquals(5, eventStore.createAdminQuery().authClient("clientId").getResultList().size());
+ Assert.assertEquals(5, eventStore.createAdminQuery().authRealm("realmId").getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().operation(OperationType.VIEW).getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().operation(OperationType.VIEW, OperationType.ACTION).getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().authUser("userId").getResultList().size());
+
+ Assert.assertEquals(1, eventStore.createAdminQuery().authUser("userId").operation(OperationType.ACTION).getResultList().size());
+
+ Assert.assertEquals(2, eventStore.createAdminQuery().maxResults(2).getResultList().size());
+ Assert.assertEquals(1, eventStore.createAdminQuery().firstResult(5).getResultList().size());
+
+ Assert.assertEquals(newest, eventStore.createAdminQuery().maxResults(1).getResultList().get(0).getTime());
+ Assert.assertEquals(oldest, eventStore.createAdminQuery().firstResult(5).maxResults(1).getResultList().get(0).getTime());
+
+ eventStore.clearAdmin("realmId");
+ eventStore.clearAdmin("realmId2");
+
+ Assert.assertEquals(0, eventStore.createAdminQuery().getResultList().size());
+
+ String d1 = new String("2015-03-04");
+ String d2 = new String("2015-03-05");
+ String d3 = new String("2015-03-06");
+ String d4 = new String("2015-03-07");
+
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+ Date date1 = null, date2 = null, date3 = null, date4 = null;
+
+ try {
+ date1 = formatter.parse(d1);
+ date2 = formatter.parse(d2);
+ date3 = formatter.parse(d3);
+ date4 = formatter.parse(d4);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ eventStore.onEvent(create(date1, OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date1, OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date2, OperationType.ACTION, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date2, OperationType.ACTION, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date3, OperationType.UPDATE, "realmId", "clientId", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date3, OperationType.DELETE, "realmId", "clientId", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date4, OperationType.CREATE, "realmId2", "clientId2", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(date4, OperationType.CREATE, "realmId2", "clientId2", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+
+ resetSession();
+
+ Assert.assertEquals(6, eventStore.createAdminQuery().authClient("clientId").getResultList().size());
+ Assert.assertEquals(2, eventStore.createAdminQuery().authClient("clientId2").getResultList().size());
+
+ Assert.assertEquals(6, eventStore.createAdminQuery().authRealm("realmId").getResultList().size());
+ Assert.assertEquals(2, eventStore.createAdminQuery().authRealm("realmId2").getResultList().size());
+
+ Assert.assertEquals(4, eventStore.createAdminQuery().authUser("userId").getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().authUser("userId2").getResultList().size());
+
+ Assert.assertEquals(2, eventStore.createAdminQuery().operation(OperationType.VIEW).getResultList().size());
+ Assert.assertEquals(2, eventStore.createAdminQuery().operation(OperationType.ACTION).getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().operation(OperationType.VIEW, OperationType.ACTION).getResultList().size());
+ Assert.assertEquals(1, eventStore.createAdminQuery().operation(OperationType.UPDATE).getResultList().size());
+ Assert.assertEquals(1, eventStore.createAdminQuery().operation(OperationType.DELETE).getResultList().size());
+ Assert.assertEquals(2, eventStore.createAdminQuery().operation(OperationType.CREATE).getResultList().size());
+
+ Assert.assertEquals(8, eventStore.createAdminQuery().fromTime("2015-03-04").getResultList().size());
+ Assert.assertEquals(8, eventStore.createAdminQuery().toTime("2015-03-07").getResultList().size());
+
+ Assert.assertEquals(4, eventStore.createAdminQuery().fromTime("2015-03-06").getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().toTime("2015-03-05").getResultList().size());
+
+ Assert.assertEquals(0, eventStore.createAdminQuery().fromTime("2015-03-08").getResultList().size());
+ Assert.assertEquals(0, eventStore.createAdminQuery().toTime("2015-03-03").getResultList().size());
+
+ Assert.assertEquals(8, eventStore.createAdminQuery().fromTime("2015-03-04").toTime("2015-03-07").getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().fromTime("2015-03-05").toTime("2015-03-07").getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().fromTime("2015-03-04").toTime("2015-03-05").getResultList().size());
+ Assert.assertEquals(4, eventStore.createAdminQuery().fromTime("2015-03-06").toTime("2015-03-07").getResultList().size());
+
+ Assert.assertEquals(0, eventStore.createAdminQuery().fromTime("2015-03-01").toTime("2015-03-03").getResultList().size());
+ Assert.assertEquals(0, eventStore.createAdminQuery().fromTime("2015-03-08").toTime("2015-03-10").getResultList().size());
+
+ }
+
+ @Test
+ public void queryResourcePath() {
+ long oldest = System.currentTimeMillis() - 30000;
+ long newest = System.currentTimeMillis() + 30000;
+
+ eventStore.onEvent(create(OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(newest, OperationType.ACTION, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(newest, OperationType.ACTION, "realmId", "clientId", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(OperationType.VIEW, "realmId2", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(oldest, OperationType.VIEW, "realmId", "clientId2", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(OperationType.VIEW, "realmId", "clientId", "userId2", "127.0.0.1", "/admin/realms/master", "error"), false);
+
+ resetSession();
+
+ Assert.assertEquals(6, eventStore.createAdminQuery().resourcePath("/admin").getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().resourcePath("/realms").getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().resourcePath("/master").getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().resourcePath("/admin/realms").getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().resourcePath("/realms/master").getResultList().size());
+ Assert.assertEquals(6, eventStore.createAdminQuery().resourcePath("/admin/realms/master").getResultList().size());
+ }
+
+ @Test
+ public void clear() {
+ eventStore.onEvent(create(System.currentTimeMillis() - 30000, OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis() - 20000, OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis(), OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis(), OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis() - 30000, OperationType.VIEW, "realmId2", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+
+ resetSession();
+
+ eventStore.clearAdmin("realmId");
+
+ Assert.assertEquals(1, eventStore.createAdminQuery().getResultList().size());
+ }
+
+ @Test
+ public void clearOld() {
+ eventStore.onEvent(create(System.currentTimeMillis() - 30000, OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis() - 20000, OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis(), OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis(), OperationType.VIEW, "realmId", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+ eventStore.onEvent(create(System.currentTimeMillis() - 30000, OperationType.VIEW, "realmId2", "clientId", "userId", "127.0.0.1", "/admin/realms/master", "error"), false);
+
+ resetSession();
+
+ eventStore.clearAdmin("realmId", System.currentTimeMillis() - 10000);
+
+ Assert.assertEquals(3, eventStore.createAdminQuery().getResultList().size());
+ }
+
+ private AdminEvent create(OperationType operation, String realmId, String clientId, String userId, String ipAddress, String resourcePath, String error) {
+ return create(System.currentTimeMillis(), operation, realmId, clientId, userId, ipAddress, resourcePath, error);
+ }
+
+ private AdminEvent create(Date date, OperationType operation, String realmId, String clientId, String userId, String ipAddress, String resourcePath, String error) {
+ return create(date.getTime(), operation, realmId, clientId, userId, ipAddress, resourcePath, error);
+ }
+
+ private AdminEvent create(long time, OperationType operation, String realmId, String clientId, String userId, String ipAddress, String resourcePath, String error) {
+ AdminEvent e = new AdminEvent();
+ e.setTime(time);
+ e.setOperationType(operation);
+ AuthDetails authDetails = new AuthDetails();
+ authDetails.setRealmId(realmId);
+ authDetails.setClientId(clientId);
+ authDetails.setUserId(userId);
+ authDetails.setIpAddress(ipAddress);
+ e.setAuthDetails(authDetails);
+ e.setResourcePath(resourcePath);
+ e.setError(error);
+
+ return e;
+ }
+
+ private void resetSession() {
+ kc.stopSession(session, true);
+ session = kc.startSession();
+ eventStore = session.getProvider(EventStoreProvider.class);
+ }
+
+}