[KEYCLOAK-392] - Admin audit events

This commit is contained in:
girirajsharma 2015-05-03 01:46:52 +05:30
parent edfe7bd285
commit 70f53c6c06
71 changed files with 3047 additions and 257 deletions

View file

@ -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>

View file

@ -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.RC1.xml"/>
<include file="META-INF/jpa-changelog-1.3.0.Beta1.xml"/>
</databaseChangeLog>

View file

@ -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();

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}
}
}
}
}

View file

@ -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()) {

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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>*&#47;master</b> - matches 'realms/master'</li>
* <li><b>**&#47;00d4b16f</b> - matches 'realms/master/clients/00d4b16f'</li>
* <li><b>realms&#47;master&#47;**</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();
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
@ -49,6 +50,11 @@ public class EmailEventListenerProvider implements EventListenerProvider {
}
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
}
@Override
public void close() {
}

View file

@ -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("]");
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
return eventEntity;
}
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());
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);
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;
@ -58,6 +59,33 @@ public class SysLoggingEventListenerProvider implements EventListenerProvider {
syslogger.log(level, sb.toString());
}
@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() {
}

View file

@ -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() {
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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
@ -64,6 +73,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() {
}

View file

@ -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

View file

@ -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 : {

View file

@ -1214,7 +1214,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;
@ -1232,7 +1232,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);
@ -1272,6 +1278,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) {
@ -1281,7 +1295,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 = {
@ -1342,6 +1356,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');

View file

@ -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');
});

View file

@ -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>

View file

@ -0,0 +1,3 @@
<div style="padding: 20px 20px 10px 20px">
<pre ng-bind = "{{event.representation}} | json"></pre>
</div>

View file

@ -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>

View file

@ -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="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>
<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>
<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="col-md-6">
<input ui-select2="eventSelectOptions" id="enabledEventTypes" ng-model="eventsConfig.enabledEventTypes" data-placeholder="Select event types..."/>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="eventsListeners" class="control-label">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>
<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>Configure what listeners receive events for the realm.</kc-tooltip>
</div>
</fieldset>
<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="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="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>
<fieldset>
<legend><span class="text">Admin Events Settings</span></legend>
<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>
<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="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>
<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>
<kc-menu></kc-menu>

View file

@ -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>

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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()));

View file

@ -958,6 +958,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;

View file

@ -750,6 +750,42 @@ public class RealmAdapter implements RealmModel {
updated.setEnabledEventTypes(enabledEventTypes);
}
@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;
}
}

View file

@ -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;
}

View file

@ -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,7 +1066,41 @@ 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;
}
}

View file

@ -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;
}

View file

@ -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,7 +986,45 @@ 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);

View file

@ -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?

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}
@ -71,7 +76,7 @@ public class ClientsResource {
rep.add(client);
}
}
adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return rep;
}
@ -89,6 +94,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");
@ -111,8 +119,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;
}

View file

@ -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();
}

View file

@ -6,8 +6,10 @@ 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.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;
@ -29,6 +31,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.io.InputStream;
import java.util.ArrayList;
@ -45,12 +48,14 @@ 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) {
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}")
@ -62,9 +67,9 @@ public class IdentityProvidersResource {
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();
}
@ -80,6 +85,9 @@ public class IdentityProvidersResource {
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;
}
@ -97,13 +105,14 @@ public class IdentityProvidersResource {
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
@ -118,7 +127,7 @@ public class IdentityProvidersResource {
for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) {
representations.add(ModelToRepresentation.toRepresentation(identityProviderModel));
}
adminEvent.operation(OperationType.VIEW).resourcePath(session.getContext().getUri().getPath()).success();
return representations;
}
@ -130,7 +139,11 @@ public class IdentityProvidersResource {
try {
this.realm.addIdentityProvider(RepresentationToModel.toModel(representation));
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");
@ -153,9 +166,10 @@ public class IdentityProvidersResource {
throw new NotFoundException("Could not find identity provider: " + alias);
}
IdentityProviderResource identityProviderResource = new IdentityProviderResource(this.auth, realm, session, identityProviderModel);
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;
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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 setEventTypes(List<String> eventTypes) {
this.eventTypes = eventTypes;
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);
m.put(n, l);
}
return m;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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
}
};
}

View file

@ -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);
}
}