parent
3ff3aeba29
commit
0bda7e6038
30 changed files with 1279 additions and 58 deletions
|
@ -30,6 +30,7 @@ import org.keycloak.events.admin.AdminEventQuery;
|
||||||
import org.keycloak.events.admin.AuthDetails;
|
import org.keycloak.events.admin.AuthDetails;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
@ -70,13 +71,13 @@ public class JpaEventStoreProvider implements EventStoreProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear(String realmId) {
|
public void clear(RealmModel realm) {
|
||||||
em.createQuery("delete from EventEntity where realmId = :realmId").setParameter("realmId", realmId).executeUpdate();
|
em.createQuery("delete from EventEntity where realmId = :realmId").setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear(String realmId, long olderThan) {
|
public void clear(RealmModel realm, long olderThan) {
|
||||||
em.createQuery("delete from EventEntity where realmId = :realmId and time < :time").setParameter("realmId", realmId).setParameter("time", olderThan).executeUpdate();
|
em.createQuery("delete from EventEntity where realmId = :realmId and time < :time").setParameter("realmId", realm.getId()).setParameter("time", olderThan).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -105,7 +106,7 @@ public class JpaEventStoreProvider implements EventStoreProvider {
|
||||||
session.realms().getRealmsStream().forEach(realm -> {
|
session.realms().getRealmsStream().forEach(realm -> {
|
||||||
if (realm.isEventsEnabled() && realm.getEventsExpiration() > 0) {
|
if (realm.isEventsEnabled() && realm.getEventsExpiration() > 0) {
|
||||||
long olderThan = Time.currentTimeMillis() - realm.getEventsExpiration() * 1000;
|
long olderThan = Time.currentTimeMillis() - realm.getEventsExpiration() * 1000;
|
||||||
clear(realm.getId(), olderThan);
|
clear(realm, olderThan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -127,13 +128,13 @@ public class JpaEventStoreProvider implements EventStoreProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearAdmin(String realmId) {
|
public void clearAdmin(RealmModel realm) {
|
||||||
em.createQuery("delete from AdminEventEntity where realmId = :realmId").setParameter("realmId", realmId).executeUpdate();
|
em.createQuery("delete from AdminEventEntity where realmId = :realmId").setParameter("realmId", realm.getId()).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearAdmin(String realmId, long olderThan) {
|
public void clearAdmin(RealmModel realm, long olderThan) {
|
||||||
em.createQuery("delete from AdminEventEntity where realmId = :realmId and time < :time").setParameter("realmId", realmId).setParameter("time", olderThan).executeUpdate();
|
em.createQuery("delete from AdminEventEntity where realmId = :realmId and time < :time").setParameter("realmId", realm.getId()).setParameter("time", olderThan).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface provides a way for marking entities that can expire. For example, user sessions are valid only
|
||||||
|
* for certain amount of time. After that time the entities can be removed from storage/omitted from query results.
|
||||||
|
*
|
||||||
|
* Presence of expired entities in the storage should be transparent to layers above the physical one. This can be
|
||||||
|
* achieved in more ways. Ideal solution is when expired entities never reach Keycloak codebase, however, this may
|
||||||
|
* not be possible for all storage implementations, therefore, we need to double-check entities validity before they
|
||||||
|
* reach logical layer, for example, before we turn entity into model.
|
||||||
|
*
|
||||||
|
* Implementation of actual removal of the entities from the storage is responsibility of each storage individually.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface ExpirableEntity extends AbstractEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a point in the time (timestamp in milliseconds since The Epoch) when this entity expires.
|
||||||
|
*
|
||||||
|
* @return a timestamp in milliseconds since The Epoch or {@code null} if this entity never expires.
|
||||||
|
*/
|
||||||
|
Long getExpiration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a point in the time (timestamp in milliseconds since The Epoch) when this entity expires.
|
||||||
|
*
|
||||||
|
* @param expiration a timestamp in milliseconds since The Epoch or {@code null} if this entity never expires.
|
||||||
|
*/
|
||||||
|
void setExpiration(Long expiration);
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.events.admin.AuthDetails;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EventUtils {
|
||||||
|
public static Event entityToModel(MapAuthEventEntity eventEntity) {
|
||||||
|
Event event = new Event();
|
||||||
|
event.setId(eventEntity.getId());
|
||||||
|
event.setTime(eventEntity.getTime());
|
||||||
|
event.setType(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());
|
||||||
|
|
||||||
|
Map<String, String> details = eventEntity.getDetails();
|
||||||
|
event.setDetails(details == null ? Collections.emptyMap() : details);
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdminEvent entityToModel(MapAdminEventEntity adminEventEntity) {
|
||||||
|
AdminEvent adminEvent = new AdminEvent();
|
||||||
|
adminEvent.setId(adminEventEntity.getId());
|
||||||
|
adminEvent.setTime(adminEventEntity.getTime());
|
||||||
|
adminEvent.setRealmId(adminEventEntity.getRealmId());
|
||||||
|
setAuthDetails(adminEvent, adminEventEntity);
|
||||||
|
adminEvent.setOperationType(adminEventEntity.getOperationType());
|
||||||
|
adminEvent.setResourceTypeAsString(adminEventEntity.getResourceType());
|
||||||
|
adminEvent.setResourcePath(adminEventEntity.getResourcePath());
|
||||||
|
adminEvent.setError(adminEventEntity.getError());
|
||||||
|
|
||||||
|
if(adminEventEntity.getRepresentation() != null) {
|
||||||
|
adminEvent.setRepresentation(adminEventEntity.getRepresentation());
|
||||||
|
}
|
||||||
|
return adminEvent;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MapAdminEventEntity modelToEntity(AdminEvent adminEvent, boolean includeRepresentation) {
|
||||||
|
MapAdminEventEntity mapAdminEvent = new MapAdminEventEntityImpl();
|
||||||
|
mapAdminEvent.setId(adminEvent.getId());
|
||||||
|
mapAdminEvent.setTime(adminEvent.getTime());
|
||||||
|
mapAdminEvent.setRealmId(adminEvent.getRealmId());
|
||||||
|
setAuthDetails(mapAdminEvent, adminEvent.getAuthDetails());
|
||||||
|
mapAdminEvent.setOperationType(adminEvent.getOperationType());
|
||||||
|
mapAdminEvent.setResourceType(adminEvent.getResourceTypeAsString());
|
||||||
|
mapAdminEvent.setResourcePath(adminEvent.getResourcePath());
|
||||||
|
mapAdminEvent.setError(adminEvent.getError());
|
||||||
|
|
||||||
|
if(includeRepresentation) {
|
||||||
|
mapAdminEvent.setRepresentation(adminEvent.getRepresentation());
|
||||||
|
}
|
||||||
|
return mapAdminEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MapAuthEventEntity modelToEntity(Event event) {
|
||||||
|
MapAuthEventEntity eventEntity = new MapAuthEventEntityImpl();
|
||||||
|
eventEntity.setId(event.getId());
|
||||||
|
eventEntity.setTime(event.getTime());
|
||||||
|
eventEntity.setType(event.getType());
|
||||||
|
eventEntity.setRealmId(event.getRealmId());
|
||||||
|
eventEntity.setClientId(event.getClientId());
|
||||||
|
eventEntity.setUserId(event.getUserId());
|
||||||
|
eventEntity.setSessionId(event.getSessionId());
|
||||||
|
eventEntity.setIpAddress(event.getIpAddress());
|
||||||
|
eventEntity.setError(event.getError());
|
||||||
|
eventEntity.setDetails(event.getDetails());
|
||||||
|
return eventEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setAuthDetails(MapAdminEventEntity adminEventEntity, AuthDetails authDetails) {
|
||||||
|
if (authDetails == null) return;
|
||||||
|
adminEventEntity.setAuthRealmId(authDetails.getRealmId());
|
||||||
|
adminEventEntity.setAuthClientId(authDetails.getClientId());
|
||||||
|
adminEventEntity.setAuthUserId(authDetails.getUserId());
|
||||||
|
adminEventEntity.setAuthIpAddress(authDetails.getIpAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setAuthDetails(AdminEvent adminEvent, MapAdminEventEntity adminEventEntity) {
|
||||||
|
AuthDetails authDetails = new AuthDetails();
|
||||||
|
authDetails.setRealmId(adminEventEntity.getAuthRealmId());
|
||||||
|
authDetails.setClientId(adminEventEntity.getAuthClientId());
|
||||||
|
authDetails.setUserId(adminEventEntity.getAuthUserId());
|
||||||
|
authDetails.setIpAddress(adminEventEntity.getAuthIpAddress());
|
||||||
|
adminEvent.setAuthDetails(authDetails);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.common.ExpirableEntity;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
|
||||||
|
@GenerateEntityImplementations(
|
||||||
|
inherits = "org.keycloak.models.map.events.MapAdminEventEntity.AbstractAdminEventEntity"
|
||||||
|
)
|
||||||
|
@DeepCloner.Root
|
||||||
|
public interface MapAdminEventEntity extends UpdatableEntity, AbstractEntity, ExpirableEntity {
|
||||||
|
|
||||||
|
public abstract class AbstractAdminEventEntity extends UpdatableEntity.Impl implements MapAdminEventEntity {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setId(String id) {
|
||||||
|
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||||
|
this.id = id;
|
||||||
|
this.updated |= id != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Long getTime();
|
||||||
|
void setTime(Long time);
|
||||||
|
|
||||||
|
String getRealmId();
|
||||||
|
void setRealmId(String realmId);
|
||||||
|
|
||||||
|
OperationType getOperationType();
|
||||||
|
void setOperationType(OperationType operationType);
|
||||||
|
|
||||||
|
String getResourcePath();
|
||||||
|
void setResourcePath(String resourcePath);
|
||||||
|
|
||||||
|
String getRepresentation();
|
||||||
|
void setRepresentation(String representation);
|
||||||
|
|
||||||
|
String getError();
|
||||||
|
void setError(String error);
|
||||||
|
|
||||||
|
String getResourceType();
|
||||||
|
void setResourceType(String resourceType);
|
||||||
|
|
||||||
|
String getAuthRealmId();
|
||||||
|
void setAuthRealmId(String realmId);
|
||||||
|
|
||||||
|
String getAuthClientId();
|
||||||
|
void setAuthClientId(String clientId);
|
||||||
|
|
||||||
|
String getAuthUserId();
|
||||||
|
void setAuthUserId(String userId);
|
||||||
|
|
||||||
|
String getAuthIpAddress();
|
||||||
|
void setAuthIpAddress(String ipAddress);
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.events.admin.AdminEvent.SearchableFields;
|
||||||
|
import org.keycloak.events.admin.AdminEventQuery;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.models.map.common.TimeAdapter;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.EQ;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.GE;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LE;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LIKE;
|
||||||
|
import static org.keycloak.models.map.storage.QueryParameters.Order.DESCENDING;
|
||||||
|
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
||||||
|
|
||||||
|
public class MapAdminEventQuery implements AdminEventQuery {
|
||||||
|
|
||||||
|
private Integer firstResult;
|
||||||
|
private Integer maxResults;
|
||||||
|
private DefaultModelCriteria<AdminEvent> mcb = criteria();
|
||||||
|
private final DefaultModelCriteria<AdminEvent> criteria = criteria();
|
||||||
|
private final Function<QueryParameters<AdminEvent>, Stream<AdminEvent>> resultProducer;
|
||||||
|
|
||||||
|
public MapAdminEventQuery(Function<QueryParameters<AdminEvent>, Stream<AdminEvent>> resultProducer) {
|
||||||
|
this.resultProducer = resultProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery realm(String realmId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.REALM_ID, EQ, realmId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery authRealm(String realmId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.AUTH_REALM_ID, EQ, realmId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery authClient(String clientId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.AUTH_CLIENT_ID, EQ, clientId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery authUser(String userId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.AUTH_USER_ID, EQ, userId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery authIpAddress(String ipAddress) {
|
||||||
|
mcb = mcb.compare(SearchableFields.AUTH_IP_ADDRESS, EQ, ipAddress);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery operation(OperationType... operations) {
|
||||||
|
mcb = mcb.compare(SearchableFields.OPERATION_TYPE, IN, Arrays.stream(operations));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery resourceType(ResourceType... resourceTypes) {
|
||||||
|
mcb = mcb.compare(SearchableFields.RESOURCE_TYPE, EQ, Arrays.stream(resourceTypes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery resourcePath(String resourcePath) {
|
||||||
|
mcb = mcb.compare(SearchableFields.RESOURCE_PATH, LIKE, resourcePath.replace('*', '%'));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery fromTime(Date fromTime) {
|
||||||
|
mcb = mcb.compare(SearchableFields.TIME, GE, fromTime.getTime());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery toTime(Date toTime) {
|
||||||
|
mcb = mcb.compare(SearchableFields.TIME, LE, toTime.getTime());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery firstResult(int first) {
|
||||||
|
firstResult = first;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery maxResults(int max) {
|
||||||
|
maxResults = max;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<AdminEvent> getResultStream() {
|
||||||
|
// Add expiration condition to not load expired events
|
||||||
|
mcb = mcb.and(
|
||||||
|
criteria.or(
|
||||||
|
criteria.compare(AdminEvent.SearchableFields.EXPIRATION, ModelCriteriaBuilder.Operator.NOT_EXISTS),
|
||||||
|
criteria.compare(AdminEvent.SearchableFields.EXPIRATION, ModelCriteriaBuilder.Operator.GT,
|
||||||
|
Time.currentTimeMillis())
|
||||||
|
));
|
||||||
|
|
||||||
|
return resultProducer.apply(QueryParameters.withCriteria(mcb)
|
||||||
|
.offset(firstResult)
|
||||||
|
.limit(maxResults)
|
||||||
|
.orderBy(SearchableFields.TIME, DESCENDING)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.common.ExpirableEntity;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@GenerateEntityImplementations(
|
||||||
|
inherits = "org.keycloak.models.map.events.MapAuthEventEntity.AbstractAuthEventEntity"
|
||||||
|
)
|
||||||
|
@DeepCloner.Root
|
||||||
|
public interface MapAuthEventEntity extends UpdatableEntity, AbstractEntity, ExpirableEntity {
|
||||||
|
|
||||||
|
public abstract class AbstractAuthEventEntity extends UpdatableEntity.Impl implements MapAuthEventEntity {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setId(String id) {
|
||||||
|
if (this.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||||
|
this.id = id;
|
||||||
|
this.updated |= id != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Long getTime();
|
||||||
|
void setTime(Long time);
|
||||||
|
|
||||||
|
EventType getType();
|
||||||
|
void setType(EventType type);
|
||||||
|
|
||||||
|
String getRealmId();
|
||||||
|
void setRealmId(String realmId);
|
||||||
|
|
||||||
|
String getClientId();
|
||||||
|
void setClientId(String clientId);
|
||||||
|
|
||||||
|
String getUserId();
|
||||||
|
void setUserId(String userId);
|
||||||
|
|
||||||
|
String getSessionId();
|
||||||
|
void setSessionId(String sessionId);
|
||||||
|
|
||||||
|
String getIpAddress();
|
||||||
|
void setIpAddress(String ipAddress);
|
||||||
|
|
||||||
|
String getError();
|
||||||
|
void setError(String error);
|
||||||
|
|
||||||
|
Map<String, String> getDetails();
|
||||||
|
void setDetails(Map<String, String> details);
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.Event.SearchableFields;
|
||||||
|
import org.keycloak.events.EventQuery;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.map.common.TimeAdapter;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.EQ;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.GE;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN;
|
||||||
|
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LE;
|
||||||
|
import static org.keycloak.models.map.storage.QueryParameters.Order.DESCENDING;
|
||||||
|
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
||||||
|
|
||||||
|
public class MapAuthEventQuery implements EventQuery {
|
||||||
|
|
||||||
|
private Integer firstResult;
|
||||||
|
private Integer maxResults;
|
||||||
|
private DefaultModelCriteria<Event> mcb = criteria();
|
||||||
|
private final DefaultModelCriteria<Event> criteria = criteria();
|
||||||
|
private final Function<QueryParameters<Event>, Stream<Event>> resultProducer;
|
||||||
|
|
||||||
|
public MapAuthEventQuery(Function<QueryParameters<Event>, Stream<Event>> resultProducer) {
|
||||||
|
this.resultProducer = resultProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery type(EventType... types) {
|
||||||
|
mcb = mcb.compare(SearchableFields.EVENT_TYPE, IN, Arrays.asList(types));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery realm(String realmId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.REALM_ID, EQ, realmId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery client(String clientId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.CLIENT_ID, EQ, clientId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery user(String userId) {
|
||||||
|
mcb = mcb.compare(SearchableFields.USER_ID, EQ, userId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery fromDate(Date fromDate) {
|
||||||
|
mcb = mcb.compare(SearchableFields.TIME, GE, fromDate.getTime());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery toDate(Date toDate) {
|
||||||
|
mcb = mcb.compare(SearchableFields.TIME, LE, toDate.getTime());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery ipAddress(String ipAddress) {
|
||||||
|
mcb = mcb.compare(SearchableFields.IP_ADDRESS, EQ, ipAddress);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery firstResult(int firstResult) {
|
||||||
|
this.firstResult = firstResult;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery maxResults(int max) {
|
||||||
|
this.maxResults = max;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<Event> getResultStream() {
|
||||||
|
// Add expiration condition to not load expired events
|
||||||
|
mcb = mcb.and(
|
||||||
|
criteria.or(
|
||||||
|
criteria.compare(Event.SearchableFields.EXPIRATION, ModelCriteriaBuilder.Operator.NOT_EXISTS),
|
||||||
|
criteria.compare(Event.SearchableFields.EXPIRATION, ModelCriteriaBuilder.Operator.GT,
|
||||||
|
Time.currentTimeMillis())
|
||||||
|
));
|
||||||
|
|
||||||
|
return resultProducer.apply(QueryParameters.withCriteria(mcb)
|
||||||
|
.offset(firstResult)
|
||||||
|
.limit(maxResults)
|
||||||
|
.orderBy(SearchableFields.TIME, DESCENDING));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.EventQuery;
|
||||||
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.events.admin.AdminEventQuery;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.map.common.ExpirableEntity;
|
||||||
|
import org.keycloak.models.map.common.TimeAdapter;
|
||||||
|
import org.keycloak.models.map.group.MapGroupProvider;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||||
|
import static org.keycloak.models.map.events.EventUtils.modelToEntity;
|
||||||
|
|
||||||
|
public class MapEventStoreProvider implements EventStoreProvider {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(MapEventStoreProvider.class);
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final MapKeycloakTransaction<MapAuthEventEntity, Event> authEventsTX;
|
||||||
|
private final MapKeycloakTransaction<MapAdminEventEntity, AdminEvent> adminEventsTX;
|
||||||
|
|
||||||
|
public MapEventStoreProvider(KeycloakSession session, MapStorage<MapAuthEventEntity, Event> loginEventsStore, MapStorage<MapAdminEventEntity, AdminEvent> adminEventsStore) {
|
||||||
|
this.session = session;
|
||||||
|
this.authEventsTX = loginEventsStore.createTransaction(session);
|
||||||
|
this.adminEventsTX = adminEventsStore.createTransaction(session);
|
||||||
|
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(this.authEventsTX);
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(this.adminEventsTX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** LOGIN EVENTS **/
|
||||||
|
@Override
|
||||||
|
public void onEvent(Event event) {
|
||||||
|
LOG.tracef("onEvent(%s)%s", event, getShortStackTrace());
|
||||||
|
String id = event.getId();
|
||||||
|
|
||||||
|
if (id != null && authEventsTX.read(id) != null) {
|
||||||
|
throw new ModelDuplicateException("Event already exists: " + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
MapAuthEventEntity entity = modelToEntity(event);
|
||||||
|
String realmId = event.getRealmId();
|
||||||
|
if (realmId != null) {
|
||||||
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
if (realm != null && realm.getEventsExpiration() > 0) {
|
||||||
|
entity.setExpiration(Time.currentTimeMillis() + (realm.getEventsExpiration() * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authEventsTX.create(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterExpired(ExpirableEntity event) {
|
||||||
|
Long expiration = event.getExpiration();
|
||||||
|
// Check if entity is expired
|
||||||
|
if (expiration != null && expiration <= Time.currentTimeMillis()) {
|
||||||
|
// Remove entity
|
||||||
|
authEventsTX.delete(event.getId());
|
||||||
|
|
||||||
|
return false; // Do not include entity in the resulting stream
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Entity is not expired
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery createQuery() {
|
||||||
|
LOG.tracef("createQuery()%s", getShortStackTrace());
|
||||||
|
return new MapAuthEventQuery(((Function<QueryParameters<Event>, Stream<MapAuthEventEntity>>) authEventsTX::read)
|
||||||
|
.andThen(s -> s.filter(this::filterExpired).map(EventUtils::entityToModel)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
LOG.tracef("clear()%s", getShortStackTrace());
|
||||||
|
authEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(RealmModel realm) {
|
||||||
|
LOG.tracef("clear(%s)%s", realm, getShortStackTrace());
|
||||||
|
authEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.<Event>criteria()
|
||||||
|
.compare(Event.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(RealmModel realm, long olderThan) {
|
||||||
|
LOG.tracef("clear(%s, %d)%s", realm, olderThan, getShortStackTrace());
|
||||||
|
authEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.<Event>criteria()
|
||||||
|
.compare(Event.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(Event.SearchableFields.TIME, ModelCriteriaBuilder.Operator.LT, olderThan)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearExpiredEvents() {
|
||||||
|
LOG.tracef("clearExpiredEvents()%s", getShortStackTrace());
|
||||||
|
|
||||||
|
authEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.<Event>criteria()
|
||||||
|
.compare(Event.SearchableFields.EXPIRATION, ModelCriteriaBuilder.Operator.LE,
|
||||||
|
Time.currentTimeMillis())));
|
||||||
|
|
||||||
|
adminEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.<AdminEvent>criteria()
|
||||||
|
.compare(AdminEvent.SearchableFields.EXPIRATION, ModelCriteriaBuilder.Operator.LE,
|
||||||
|
Time.currentTimeMillis())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ADMIN EVENTS **/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||||
|
LOG.tracef("clear(%s, %s)%s", event, includeRepresentation, getShortStackTrace());
|
||||||
|
String id = event.getId();
|
||||||
|
if (id != null && authEventsTX.read(id) != null) {
|
||||||
|
throw new ModelDuplicateException("Event already exists: " + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
adminEventsTX.create(modelToEntity(event, includeRepresentation));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdminEventQuery createAdminQuery() {
|
||||||
|
LOG.tracef("createAdminQuery()%s", getShortStackTrace());
|
||||||
|
return new MapAdminEventQuery(((Function<QueryParameters<AdminEvent>, Stream<MapAdminEventEntity>>) adminEventsTX::read)
|
||||||
|
.andThen(s -> s.filter(this::filterExpired).map(EventUtils::entityToModel)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAdmin() {
|
||||||
|
LOG.tracef("clearAdmin()%s", getShortStackTrace());
|
||||||
|
adminEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.criteria()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAdmin(RealmModel realm) {
|
||||||
|
LOG.tracef("clear(%s)%s", realm, getShortStackTrace());
|
||||||
|
adminEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.<AdminEvent>criteria()
|
||||||
|
.compare(AdminEvent.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAdmin(RealmModel realm, long olderThan) {
|
||||||
|
LOG.tracef("clearAdmin(%s, %d)%s", realm, olderThan, getShortStackTrace());
|
||||||
|
adminEventsTX.delete(QueryParameters.withCriteria(DefaultModelCriteria.<AdminEvent>criteria()
|
||||||
|
.compare(AdminEvent.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, realm.getId())
|
||||||
|
.compare(AdminEvent.SearchableFields.TIME, ModelCriteriaBuilder.Operator.LT, olderThan)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.events;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.component.AmphibianProviderFactory;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
import org.keycloak.events.EventStoreProviderFactory;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.MapStorageSpi;
|
||||||
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
import org.keycloak.provider.InvalidationHandler;
|
||||||
|
|
||||||
|
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
|
||||||
|
import static org.keycloak.models.map.common.AbstractMapProviderFactory.uniqueCounter;
|
||||||
|
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
|
||||||
|
|
||||||
|
public class MapEventStoreProviderFactory implements AmphibianProviderFactory<EventStoreProvider>, EnvironmentDependentProviderFactory, EventStoreProviderFactory, InvalidationHandler {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID;
|
||||||
|
private Config.Scope storageConfigScopeAdminEvents;
|
||||||
|
private Config.Scope storageConfigScopeLoginEvents;
|
||||||
|
private final String uniqueKey = getClass().getName() + uniqueCounter.incrementAndGet();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
storageConfigScopeAdminEvents = config.scope(AbstractMapProviderFactory.CONFIG_STORAGE + "-admin-events");
|
||||||
|
storageConfigScopeLoginEvents = config.scope(AbstractMapProviderFactory.CONFIG_STORAGE + "-auth-events");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventStoreProvider create(KeycloakSession session) {
|
||||||
|
MapEventStoreProvider provider = session.getAttribute(uniqueKey, MapEventStoreProvider.class);
|
||||||
|
if (provider != null) return provider;
|
||||||
|
|
||||||
|
MapStorageProviderFactory storageProviderFactoryAe = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
|
||||||
|
MapStorageProvider.class, storageConfigScopeAdminEvents, MapStorageSpi.NAME);
|
||||||
|
final MapStorageProvider factoryAe = storageProviderFactoryAe.create(session);
|
||||||
|
MapStorage adminEventsStore = factoryAe.getStorage(AdminEvent.class);
|
||||||
|
|
||||||
|
MapStorageProviderFactory storageProviderFactoryLe = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
|
||||||
|
MapStorageProvider.class, storageConfigScopeLoginEvents, MapStorageSpi.NAME);
|
||||||
|
final MapStorageProvider factoryLe = storageProviderFactoryLe.create(session);
|
||||||
|
MapStorage loginEventsStore = factoryLe.getStorage(Event.class);
|
||||||
|
|
||||||
|
provider = new MapEventStoreProvider(session, loginEventsStore, adminEventsStore);
|
||||||
|
session.setAttribute(uniqueKey, provider);
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidate(KeycloakSession session, InvalidationHandler.InvalidableObjectType type, Object... params) {
|
||||||
|
if (type == REALM_BEFORE_REMOVE) {
|
||||||
|
create(session).clear((RealmModel) params[0]);
|
||||||
|
create(session).clearAdmin((RealmModel) params[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
AmphibianProviderFactory.super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Event provider";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ import org.keycloak.authorization.model.PermissionTicket;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
@ -38,6 +40,8 @@ import org.keycloak.models.map.authorization.entity.MapScopeEntity;
|
||||||
import org.keycloak.models.map.client.MapClientEntity;
|
import org.keycloak.models.map.client.MapClientEntity;
|
||||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAdminEventEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAuthEventEntity;
|
||||||
import org.keycloak.models.map.group.MapGroupEntity;
|
import org.keycloak.models.map.group.MapGroupEntity;
|
||||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
|
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
|
||||||
import org.keycloak.models.map.realm.MapRealmEntity;
|
import org.keycloak.models.map.realm.MapRealmEntity;
|
||||||
|
@ -77,6 +81,10 @@ public class ModelEntityUtil {
|
||||||
MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers");
|
MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers");
|
||||||
MODEL_TO_NAME.put(Resource.class, "authz-resources");
|
MODEL_TO_NAME.put(Resource.class, "authz-resources");
|
||||||
MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes");
|
MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes");
|
||||||
|
|
||||||
|
// events
|
||||||
|
MODEL_TO_NAME.put(AdminEvent.class, "admin-events");
|
||||||
|
MODEL_TO_NAME.put(Event.class, "auth-events");
|
||||||
}
|
}
|
||||||
private static final Map<String, Class<?>> NAME_TO_MODEL = MODEL_TO_NAME.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
|
private static final Map<String, Class<?>> NAME_TO_MODEL = MODEL_TO_NAME.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
|
||||||
|
|
||||||
|
@ -99,6 +107,10 @@ public class ModelEntityUtil {
|
||||||
MODEL_TO_ENTITY_TYPE.put(ResourceServer.class, MapResourceServerEntity.class);
|
MODEL_TO_ENTITY_TYPE.put(ResourceServer.class, MapResourceServerEntity.class);
|
||||||
MODEL_TO_ENTITY_TYPE.put(Resource.class, MapResourceEntity.class);
|
MODEL_TO_ENTITY_TYPE.put(Resource.class, MapResourceEntity.class);
|
||||||
MODEL_TO_ENTITY_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class);
|
MODEL_TO_ENTITY_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class);
|
||||||
|
|
||||||
|
// events
|
||||||
|
MODEL_TO_ENTITY_TYPE.put(AdminEvent.class, MapAdminEventEntity.class);
|
||||||
|
MODEL_TO_ENTITY_TYPE.put(Event.class, MapAuthEventEntity.class);
|
||||||
}
|
}
|
||||||
private static final Map<Class<?>, Class<?>> ENTITY_TO_MODEL_TYPE = MODEL_TO_ENTITY_TYPE.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
|
private static final Map<Class<?>, Class<?>> ENTITY_TO_MODEL_TYPE = MODEL_TO_ENTITY_TYPE.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,10 @@ import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import org.keycloak.models.map.common.DeepCloner;
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
import org.keycloak.models.map.common.Serialization;
|
import org.keycloak.models.map.common.Serialization;
|
||||||
import org.keycloak.models.map.common.UpdatableEntity;
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAdminEventEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAdminEventEntityImpl;
|
||||||
|
import org.keycloak.models.map.events.MapAuthEventEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAuthEventEntityImpl;
|
||||||
import org.keycloak.models.map.group.MapGroupEntityImpl;
|
import org.keycloak.models.map.group.MapGroupEntityImpl;
|
||||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
|
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
|
||||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityImpl;
|
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntityImpl;
|
||||||
|
@ -137,6 +141,8 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
|
||||||
.constructor(MapUserLoginFailureEntity.class, MapUserLoginFailureEntityImpl::new)
|
.constructor(MapUserLoginFailureEntity.class, MapUserLoginFailureEntityImpl::new)
|
||||||
.constructor(MapUserSessionEntity.class, MapUserSessionEntityImpl::new)
|
.constructor(MapUserSessionEntity.class, MapUserSessionEntityImpl::new)
|
||||||
.constructor(MapAuthenticatedClientSessionEntity.class, MapAuthenticatedClientSessionEntityImpl::new)
|
.constructor(MapAuthenticatedClientSessionEntity.class, MapAuthenticatedClientSessionEntityImpl::new)
|
||||||
|
.constructor(MapAuthEventEntity.class, MapAuthEventEntityImpl::new)
|
||||||
|
.constructor(MapAdminEventEntity.class, MapAdminEventEntityImpl::new)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final Map<String, StringKeyConverter> KEY_CONVERTERS = new HashMap<>();
|
private static final Map<String, StringKeyConverter> KEY_CONVERTERS = new HashMap<>();
|
||||||
|
|
|
@ -43,6 +43,7 @@ class CriteriaOperator {
|
||||||
|
|
||||||
private static final Predicate<Object> ALWAYS_FALSE = o -> false;
|
private static final Predicate<Object> ALWAYS_FALSE = o -> false;
|
||||||
private static final Predicate<Object> ALWAYS_TRUE = o -> true;
|
private static final Predicate<Object> ALWAYS_TRUE = o -> true;
|
||||||
|
private static final Pattern LIKE_PATTERN_DELIMITER = Pattern.compile("%+");
|
||||||
|
|
||||||
static {
|
static {
|
||||||
OPERATORS.put(Operator.EQ, CriteriaOperator::eq);
|
OPERATORS.put(Operator.EQ, CriteriaOperator::eq);
|
||||||
|
@ -194,15 +195,7 @@ class CriteriaOperator {
|
||||||
return ALWAYS_TRUE;
|
return ALWAYS_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean anyBeginning = sValue.startsWith("%");
|
Pattern pValue = Pattern.compile(quoteRegex(sValue), Pattern.DOTALL);
|
||||||
boolean anyEnd = sValue.endsWith("%");
|
|
||||||
|
|
||||||
Pattern pValue = Pattern.compile(
|
|
||||||
(anyBeginning ? ".*" : "")
|
|
||||||
+ Pattern.quote(sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0)))
|
|
||||||
+ (anyEnd ? ".*" : ""),
|
|
||||||
Pattern.DOTALL
|
|
||||||
);
|
|
||||||
return o -> {
|
return o -> {
|
||||||
return o instanceof String && pValue.matcher((String) o).matches();
|
return o instanceof String && pValue.matcher((String) o).matches();
|
||||||
};
|
};
|
||||||
|
@ -210,6 +203,12 @@ class CriteriaOperator {
|
||||||
return ALWAYS_FALSE;
|
return ALWAYS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String quoteRegex(String pattern) {
|
||||||
|
return LIKE_PATTERN_DELIMITER.splitAsStream(pattern).map(Pattern::quote)
|
||||||
|
.collect(Collectors.joining(".*"))
|
||||||
|
+ (pattern.endsWith("%") ? ".*" : "");
|
||||||
|
}
|
||||||
|
|
||||||
public static Predicate<Object> ilike(Object[] value) {
|
public static Predicate<Object> ilike(Object[] value) {
|
||||||
Object value0 = getFirstArrayElement(value);
|
Object value0 = getFirstArrayElement(value);
|
||||||
if (value0 instanceof String) {
|
if (value0 instanceof String) {
|
||||||
|
@ -219,15 +218,7 @@ class CriteriaOperator {
|
||||||
return ALWAYS_TRUE;
|
return ALWAYS_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean anyBeginning = sValue.startsWith("%");
|
Pattern pValue = Pattern.compile(quoteRegex(sValue), Pattern.CASE_INSENSITIVE + Pattern.DOTALL);
|
||||||
boolean anyEnd = sValue.endsWith("%");
|
|
||||||
|
|
||||||
Pattern pValue = Pattern.compile(
|
|
||||||
(anyBeginning ? ".*" : "")
|
|
||||||
+ Pattern.quote(sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0)))
|
|
||||||
+ (anyEnd ? ".*" : ""),
|
|
||||||
Pattern.CASE_INSENSITIVE + Pattern.DOTALL
|
|
||||||
);
|
|
||||||
return o -> {
|
return o -> {
|
||||||
return o instanceof String && pValue.matcher((String) o).matches();
|
return o instanceof String && pValue.matcher((String) o).matches();
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,8 @@ import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.Resource;
|
import org.keycloak.authorization.model.Resource;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.authorization.model.Scope;
|
import org.keycloak.authorization.model.Scope;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
@ -39,6 +41,8 @@ import org.keycloak.models.map.authorization.entity.MapScopeEntity;
|
||||||
import org.keycloak.models.map.client.MapClientEntity;
|
import org.keycloak.models.map.client.MapClientEntity;
|
||||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAdminEventEntity;
|
||||||
|
import org.keycloak.models.map.events.MapAuthEventEntity;
|
||||||
import org.keycloak.models.map.group.MapGroupEntity;
|
import org.keycloak.models.map.group.MapGroupEntity;
|
||||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
|
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
|
||||||
import org.keycloak.models.map.realm.MapRealmEntity;
|
import org.keycloak.models.map.realm.MapRealmEntity;
|
||||||
|
@ -92,6 +96,8 @@ public class MapFieldPredicates {
|
||||||
public static final Map<SearchableModelField<UserLoginFailureModel>, UpdatePredicatesFunc<Object, MapUserLoginFailureEntity, UserLoginFailureModel>> USER_LOGIN_FAILURE_PREDICATES = basePredicates(UserLoginFailureModel.SearchableFields.ID);
|
public static final Map<SearchableModelField<UserLoginFailureModel>, UpdatePredicatesFunc<Object, MapUserLoginFailureEntity, UserLoginFailureModel>> USER_LOGIN_FAILURE_PREDICATES = basePredicates(UserLoginFailureModel.SearchableFields.ID);
|
||||||
public static final Map<SearchableModelField<UserModel>, UpdatePredicatesFunc<Object, MapUserEntity, UserModel>> USER_PREDICATES = basePredicates(UserModel.SearchableFields.ID);
|
public static final Map<SearchableModelField<UserModel>, UpdatePredicatesFunc<Object, MapUserEntity, UserModel>> USER_PREDICATES = basePredicates(UserModel.SearchableFields.ID);
|
||||||
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, MapUserSessionEntity, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
|
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, MapUserSessionEntity, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
|
||||||
|
public static final Map<SearchableModelField<Event>, UpdatePredicatesFunc<Object, MapAuthEventEntity, Event>> AUTH_EVENTS_PREDICATES = basePredicates(Event.SearchableFields.ID);
|
||||||
|
public static final Map<SearchableModelField<AdminEvent>, UpdatePredicatesFunc<Object, MapAdminEventEntity, AdminEvent>> ADMIN_EVENTS_PREDICATES = basePredicates(AdminEvent.SearchableFields.ID);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
|
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
|
||||||
|
@ -201,6 +207,25 @@ public class MapFieldPredicates {
|
||||||
|
|
||||||
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.REALM_ID, MapUserLoginFailureEntity::getRealmId);
|
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.REALM_ID, MapUserLoginFailureEntity::getRealmId);
|
||||||
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.USER_ID, MapUserLoginFailureEntity::getUserId);
|
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.USER_ID, MapUserLoginFailureEntity::getUserId);
|
||||||
|
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.REALM_ID, MapAuthEventEntity::getRealmId);
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.CLIENT_ID, MapAuthEventEntity::getClientId);
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.USER_ID, MapAuthEventEntity::getUserId);
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.TIME, MapAuthEventEntity::getTime);
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.EXPIRATION, MapAuthEventEntity::getExpiration);
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.IP_ADDRESS, MapAuthEventEntity::getIpAddress);
|
||||||
|
put(AUTH_EVENTS_PREDICATES, Event.SearchableFields.EVENT_TYPE, MapAuthEventEntity::getType);
|
||||||
|
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.REALM_ID, MapAdminEventEntity::getRealmId);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.TIME, MapAdminEventEntity::getTime);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.EXPIRATION, MapAdminEventEntity::getExpiration);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_REALM_ID, MapAdminEventEntity::getAuthRealmId);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_CLIENT_ID, MapAdminEventEntity::getAuthClientId);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_USER_ID, MapAdminEventEntity::getAuthUserId);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.AUTH_IP_ADDRESS, MapAdminEventEntity::getAuthIpAddress);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.OPERATION_TYPE, MapAdminEventEntity::getOperationType);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_TYPE, MapAdminEventEntity::getResourceType);
|
||||||
|
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_PATH, MapAdminEventEntity::getResourcePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -219,6 +244,8 @@ public class MapFieldPredicates {
|
||||||
PREDICATES.put(UserSessionModel.class, USER_SESSION_PREDICATES);
|
PREDICATES.put(UserSessionModel.class, USER_SESSION_PREDICATES);
|
||||||
PREDICATES.put(AuthenticatedClientSessionModel.class, CLIENT_SESSION_PREDICATES);
|
PREDICATES.put(AuthenticatedClientSessionModel.class, CLIENT_SESSION_PREDICATES);
|
||||||
PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES);
|
PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES);
|
||||||
|
PREDICATES.put(Event.class, AUTH_EVENTS_PREDICATES);
|
||||||
|
PREDICATES.put(AdminEvent.class, ADMIN_EVENTS_PREDICATES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <K, V extends AbstractEntity, M, L extends Comparable<L>> void put(
|
private static <K, V extends AbstractEntity, M, L extends Comparable<L>> void put(
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.models.map.events.MapEventStoreProviderFactory
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.events;
|
package org.keycloak.events;
|
||||||
|
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -25,6 +27,17 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class Event {
|
public class Event {
|
||||||
|
|
||||||
|
public static class SearchableFields {
|
||||||
|
public static final SearchableModelField<Event> ID = new SearchableModelField<>("id", String.class);
|
||||||
|
public static final SearchableModelField<Event> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||||
|
public static final SearchableModelField<Event> CLIENT_ID = new SearchableModelField<>("clientId", String.class);
|
||||||
|
public static final SearchableModelField<Event> USER_ID = new SearchableModelField<>("userId", String.class);
|
||||||
|
public static final SearchableModelField<Event> TIME = new SearchableModelField<>("time", Long.class);
|
||||||
|
public static final SearchableModelField<Event> EXPIRATION = new SearchableModelField<>("expiration", Long.class);
|
||||||
|
public static final SearchableModelField<Event> IP_ADDRESS = new SearchableModelField<>("ipAddress", String.class);
|
||||||
|
public static final SearchableModelField<Event> EVENT_TYPE = new SearchableModelField<>("eventType", EventType.class);
|
||||||
|
}
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
private long time;
|
private long time;
|
||||||
|
|
|
@ -27,23 +27,68 @@ import java.util.stream.Stream;
|
||||||
*/
|
*/
|
||||||
public interface EventQuery {
|
public interface EventQuery {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events with given types
|
||||||
|
* @param types requested types
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery type(EventType... types);
|
EventQuery type(EventType... types);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events within realm
|
||||||
|
* @param realmId id of realm
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery realm(String realmId);
|
EventQuery realm(String realmId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events for only one client
|
||||||
|
* @param clientId id of client
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery client(String clientId);
|
EventQuery client(String clientId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events for only one user
|
||||||
|
* @param userId id of user
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery user(String userId);
|
EventQuery user(String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events that are newer than {@code fromDate}
|
||||||
|
* @param fromDate date
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery fromDate(Date fromDate);
|
EventQuery fromDate(Date fromDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events that are older than {@code toDate}
|
||||||
|
* @param toDate date
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery toDate(Date toDate);
|
EventQuery toDate(Date toDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search events from ipAddress
|
||||||
|
* @param ipAddress ip
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
EventQuery ipAddress(String ipAddress);
|
EventQuery ipAddress(String ipAddress);
|
||||||
|
|
||||||
EventQuery firstResult(int result);
|
/**
|
||||||
|
* Index of the first result to return.
|
||||||
|
* @param firstResult the index. Ignored if negative.
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
|
EventQuery firstResult(int firstResult);
|
||||||
|
|
||||||
EventQuery maxResults(int results);
|
/**
|
||||||
|
* Maximum number of results to return.
|
||||||
|
* @param max a number. Ignored if negative.
|
||||||
|
* @return this object for method chaining
|
||||||
|
*/
|
||||||
|
EventQuery maxResults(int max);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #getResultStream() getResultStream} instead.
|
* @deprecated Use {@link #getResultStream() getResultStream} instead.
|
||||||
|
|
|
@ -18,31 +18,86 @@
|
||||||
package org.keycloak.events;
|
package org.keycloak.events;
|
||||||
|
|
||||||
import org.keycloak.events.admin.AdminEventQuery;
|
import org.keycloak.events.admin.AdminEventQuery;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface EventStoreProvider extends EventListenerProvider {
|
public interface EventStoreProvider extends EventListenerProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object representing auth event query of type {@link EventQuery}.
|
||||||
|
*
|
||||||
|
* The object is used for collecting requested properties of auth events (e.g. realm, operation, resourceType
|
||||||
|
* time boundaries, etc.) and contains the {@link EventQuery#getResultStream()} method that returns all
|
||||||
|
* objects from this store provider that have given properties.
|
||||||
|
*
|
||||||
|
* @return a query object
|
||||||
|
*/
|
||||||
EventQuery createQuery();
|
EventQuery createQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object representing admin event query of type {@link AdminEventQuery}.
|
||||||
|
*
|
||||||
|
* The object is used for collecting requested properties of admin events (e.g. realm, operation, resourceType
|
||||||
|
* time boundaries, etc.) and contains the {@link AdminEventQuery#getResultStream()} method that returns all
|
||||||
|
* objects from this store provider that have given properties.
|
||||||
|
*
|
||||||
|
* @return a query object
|
||||||
|
*/
|
||||||
AdminEventQuery createAdminQuery();
|
AdminEventQuery createAdminQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all auth events from this store provider.
|
||||||
|
*
|
||||||
|
* @deprecated Unused method. Currently, used only in the testsuite
|
||||||
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
void clear(String realmId);
|
/**
|
||||||
|
* Removes all auth events for the realm from this store provider.
|
||||||
void clear(String realmId, long olderThan);
|
* @param realm the realm
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void clear(RealmModel realm);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all expired events in all realms
|
* Removes all auth events for the realm that are older than {@code olderThan} from this store provider.
|
||||||
|
*
|
||||||
|
* @param realm the realm
|
||||||
|
* @param olderThan point in time in milliseconds
|
||||||
|
*/
|
||||||
|
void clear(RealmModel realm, long olderThan);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all expired events in all realms
|
||||||
|
*
|
||||||
|
* @deprecated This method is problem from the performance perspective. Some storages can provide better way
|
||||||
|
* for doing this (e.g. entry lifespan in the Infinispan server, etc.). We need to leave solving event expiration
|
||||||
|
* to each storage provider separately using expiration field on entity level.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
void clearExpiredEvents();
|
void clearExpiredEvents();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all admin events from this store provider.
|
||||||
|
*
|
||||||
|
* @deprecated Unused method. Currently, used only in the testsuite
|
||||||
|
*/
|
||||||
void clearAdmin();
|
void clearAdmin();
|
||||||
|
|
||||||
void clearAdmin(String realmId);
|
/**
|
||||||
|
* Removes all auth events for the realm from this store provider.
|
||||||
|
* @param realm the realm
|
||||||
|
*/
|
||||||
|
void clearAdmin(RealmModel realm);
|
||||||
|
|
||||||
void clearAdmin(String realmId, long olderThan);
|
/**
|
||||||
|
* Removes all auth events for the realm that are older than {@code olderThan} from this store provider.
|
||||||
|
*
|
||||||
|
* @param realm the realm
|
||||||
|
* @param olderThan point in time in milliseconds
|
||||||
|
*/
|
||||||
|
void clearAdmin(RealmModel realm, long olderThan);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.keycloak.provider.Spi;
|
||||||
*/
|
*/
|
||||||
public class EventStoreSpi implements Spi {
|
public class EventStoreSpi implements Spi {
|
||||||
|
|
||||||
|
public static final String NAME = "eventsStore";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternal() {
|
public boolean isInternal() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -33,7 +35,7 @@ public class EventStoreSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "eventsStore";
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,11 +17,27 @@
|
||||||
|
|
||||||
package org.keycloak.events.admin;
|
package org.keycloak.events.admin;
|
||||||
|
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class AdminEvent {
|
public class AdminEvent {
|
||||||
|
|
||||||
|
public static class SearchableFields {
|
||||||
|
public static final SearchableModelField<AdminEvent> ID = new SearchableModelField<>("id", String.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> TIME = new SearchableModelField<>("time", Long.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> EXPIRATION = new SearchableModelField<>("expiration", Long.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> AUTH_REALM_ID = new SearchableModelField<>("authRealmId", String.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> AUTH_CLIENT_ID = new SearchableModelField<>("authClientId", String.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> AUTH_USER_ID = new SearchableModelField<>("authUserId", String.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> AUTH_IP_ADDRESS = new SearchableModelField<>("authIpAddress", String.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> OPERATION_TYPE = new SearchableModelField<>("operationType", OperationType.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> RESOURCE_TYPE = new SearchableModelField<>("resourceType", ResourceType.class);
|
||||||
|
public static final SearchableModelField<AdminEvent> RESOURCE_PATH = new SearchableModelField<>("resourcePath", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
private long time;
|
private long time;
|
||||||
|
|
|
@ -904,7 +904,7 @@ public class RealmAdminResource {
|
||||||
auth.realm().requireManageEvents();
|
auth.realm().requireManageEvents();
|
||||||
|
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clear(realm.getId());
|
eventStore.clear(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -917,7 +917,7 @@ public class RealmAdminResource {
|
||||||
auth.realm().requireManageEvents();
|
auth.realm().requireManageEvents();
|
||||||
|
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clearAdmin(realm.getId());
|
eventStore.clearAdmin(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authorization.policy.evaluation.Realm;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.HtmlUtils;
|
import org.keycloak.common.util.HtmlUtils;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
@ -299,7 +300,9 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response clearEventStore(@QueryParam("realmId") String realmId) {
|
public Response clearEventStore(@QueryParam("realmId") String realmId) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clear(realmId);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
eventStore.clear(realm);
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,7 +425,9 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response clearAdminEventStore(@QueryParam("realmId") String realmId) {
|
public Response clearAdminEventStore(@QueryParam("realmId") String realmId) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clearAdmin(realmId);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
|
||||||
|
eventStore.clearAdmin(realm);
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,7 +436,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response clearAdminEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
|
public Response clearAdminEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clearAdmin(realmId, olderThan);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
eventStore.clearAdmin(realm, olderThan);
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -880,6 +880,7 @@
|
||||||
<keycloak.userSession.provider>map</keycloak.userSession.provider>
|
<keycloak.userSession.provider>map</keycloak.userSession.provider>
|
||||||
<keycloak.loginFailure.provider>map</keycloak.loginFailure.provider>
|
<keycloak.loginFailure.provider>map</keycloak.loginFailure.provider>
|
||||||
<keycloak.authorization.provider>map</keycloak.authorization.provider>
|
<keycloak.authorization.provider>map</keycloak.authorization.provider>
|
||||||
|
<keycloak.eventsStore.provider>map</keycloak.eventsStore.provider>
|
||||||
<keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled>
|
<keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled>
|
||||||
<keycloak.realmCache.enabled>false</keycloak.realmCache.enabled>
|
<keycloak.realmCache.enabled>false</keycloak.realmCache.enabled>
|
||||||
<keycloak.userCache.enabled>false</keycloak.userCache.enabled>
|
<keycloak.userCache.enabled>false</keycloak.userCache.enabled>
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.RealmProvider;
|
import org.keycloak.models.RealmProvider;
|
||||||
import org.keycloak.models.cache.CacheRealmProvider;
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
|
@ -735,9 +736,13 @@ public abstract class AbstractKeycloakTest {
|
||||||
* returns true if realm provider is "jpa" to be able to skip particular tests.
|
* returns true if realm provider is "jpa" to be able to skip particular tests.
|
||||||
*/
|
*/
|
||||||
protected boolean isJpaRealmProvider() {
|
protected boolean isJpaRealmProvider() {
|
||||||
String realmProvider = testingClient.server()
|
return keycloakUsingProviderWithId(RealmProvider.class, "jpa");
|
||||||
.fetchString(s -> s.getKeycloakSessionFactory().getProviderFactory(RealmProvider.class).getId());
|
}
|
||||||
return Objects.equals(realmProvider, "\"jpa\"");
|
|
||||||
|
protected boolean keycloakUsingProviderWithId(Class<? extends Provider> providerClass, String requiredId) {
|
||||||
|
String providerId = testingClient.server()
|
||||||
|
.fetchString(s -> s.getKeycloakSessionFactory().getProviderFactory(providerClass).getId());
|
||||||
|
return Objects.equals(providerId, "\"" + requiredId + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isRealmCacheEnabled() {
|
protected boolean isRealmCacheEnabled() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.testsuite.events;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.client.resources.TestingResource;
|
import org.keycloak.testsuite.client.resources.TestingResource;
|
||||||
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -32,6 +33,14 @@ public abstract class AbstractEventsTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
RealmRepresentation rep1 = RealmBuilder.create().name("realmId").build();
|
||||||
|
rep1.setId("realmId");
|
||||||
|
|
||||||
|
RealmRepresentation rep2 = RealmBuilder.create().name("realmId2").build();
|
||||||
|
rep2.setId("realmId2");
|
||||||
|
|
||||||
|
testRealms.add(rep1);
|
||||||
|
testRealms.add(rep2);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TestingResource testing() {
|
protected TestingResource testing() {
|
||||||
|
|
|
@ -20,9 +20,11 @@ package org.keycloak.testsuite.events;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Assume;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.EventStoreProvider;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
|
import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
@ -209,6 +211,7 @@ public class EventStoreProviderTest extends AbstractEventsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clearOld() {
|
public void clearOld() {
|
||||||
|
Assume.assumeTrue("Map storage event store provider does not support changing expiration of existing events", keycloakUsingProviderWithId(EventStoreProvider.class, "jpa"));
|
||||||
testing().onEvent(create(System.currentTimeMillis() - 300000, EventType.LOGIN, realmId, "clientId", "userId", "127.0.0.1", "error"));
|
testing().onEvent(create(System.currentTimeMillis() - 300000, EventType.LOGIN, realmId, "clientId", "userId", "127.0.0.1", "error"));
|
||||||
testing().onEvent(create(System.currentTimeMillis() - 200000, EventType.LOGIN, realmId, "clientId", "userId", "127.0.0.1", "error"));
|
testing().onEvent(create(System.currentTimeMillis() - 200000, EventType.LOGIN, realmId, "clientId", "userId", "127.0.0.1", "error"));
|
||||||
testing().onEvent(create(System.currentTimeMillis(), EventType.LOGIN, realmId, "clientId", "userId", "127.0.0.1", "error"));
|
testing().onEvent(create(System.currentTimeMillis(), EventType.LOGIN, realmId, "clientId", "userId", "127.0.0.1", "error"));
|
||||||
|
|
|
@ -25,6 +25,14 @@
|
||||||
"provider": "${keycloak.eventsStore.provider:jpa}",
|
"provider": "${keycloak.eventsStore.provider:jpa}",
|
||||||
"jpa": {
|
"jpa": {
|
||||||
"max-detail-length": "${keycloak.eventsStore.maxDetailLength:1000}"
|
"max-detail-length": "${keycloak.eventsStore.maxDetailLength:1000}"
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"storage-admin-events": {
|
||||||
|
"provider": "${keycloak.eventStore.map.storage.provider:concurrenthashmap}"
|
||||||
|
},
|
||||||
|
"storage-auth-events": {
|
||||||
|
"provider": "${keycloak.eventStore.map.storage.provider:concurrenthashmap}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,25 @@
|
||||||
package org.keycloak.testsuite.model.events;
|
package org.keycloak.testsuite.model.events;
|
||||||
|
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventStoreProvider;
|
import org.keycloak.events.EventStoreProvider;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.testsuite.model.KeycloakModelTest;
|
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||||
import org.keycloak.testsuite.model.RequireProvider;
|
import org.keycloak.testsuite.model.RequireProvider;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +45,19 @@ import static org.hamcrest.Matchers.is;
|
||||||
@RequireProvider(EventStoreProvider.class)
|
@RequireProvider(EventStoreProvider.class)
|
||||||
public class AdminEventQueryTest extends KeycloakModelTest {
|
public class AdminEventQueryTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("realm");
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClear() {
|
public void testClear() {
|
||||||
|
@ -44,28 +67,118 @@ public class AdminEventQueryTest extends KeycloakModelTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Event createAuthEventForUser(RealmModel realm, String user) {
|
||||||
|
return new EventBuilder(realm, null, DummyClientConnection.DUMMY_CONNECTION)
|
||||||
|
.event(EventType.LOGIN)
|
||||||
|
.user(user)
|
||||||
|
.getEvent();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testQuery() {
|
public void testQuery() {
|
||||||
inRolledBackTransaction(null, (session, t) -> {
|
withRealm(realmId, (session, realm) -> {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
RealmModel realm = session.realms().createRealm("realm");
|
eventStore.onEvent(createAuthEventForUser(realm,"u1"));
|
||||||
ClientConnection cc = new DummyClientConnection();
|
eventStore.onEvent(createAuthEventForUser(realm,"u2"));
|
||||||
eventStore.onEvent(new EventBuilder(realm, null, cc).event(EventType.LOGIN).user("u1").getEvent());
|
eventStore.onEvent(createAuthEventForUser(realm,"u3"));
|
||||||
eventStore.onEvent(new EventBuilder(realm, null, cc).event(EventType.LOGIN).user("u2").getEvent());
|
eventStore.onEvent(createAuthEventForUser(realm,"u4"));
|
||||||
eventStore.onEvent(new EventBuilder(realm, null, cc).event(EventType.LOGIN).user("u3").getEvent());
|
|
||||||
eventStore.onEvent(new EventBuilder(realm, null, cc).event(EventType.LOGIN).user("u4").getEvent());
|
|
||||||
|
|
||||||
|
return realm.getId();
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
assertThat(eventStore.createQuery()
|
assertThat(eventStore.createQuery()
|
||||||
.firstResult(2)
|
.firstResult(2)
|
||||||
.getResultStream()
|
.getResultStream()
|
||||||
.collect(Collectors.counting()),
|
.collect(Collectors.counting()),
|
||||||
is(2L)
|
is(2L)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequireProvider(value = EventStoreProvider.class, only = "map")
|
||||||
|
public void testEventExpiration() {
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
|
|
||||||
|
// Set expiration so no event is valid
|
||||||
|
realm.setEventsExpiration(5);
|
||||||
|
Event e = createAuthEventForUser(realm, "u1");
|
||||||
|
eventStore.onEvent(e);
|
||||||
|
|
||||||
|
// Set expiration to 1000 seconds
|
||||||
|
realm.setEventsExpiration(1000);
|
||||||
|
e = createAuthEventForUser(realm, "u2");
|
||||||
|
eventStore.onEvent(e);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
Time.setOffset(10);
|
||||||
|
|
||||||
|
try {
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
|
|
||||||
|
Set<Event> events = eventStore.createQuery()
|
||||||
|
.getResultStream().collect(Collectors.toSet());
|
||||||
|
|
||||||
|
assertThat(events, hasSize(1));
|
||||||
|
assertThat(events.iterator().next().getUserId(), equalTo("u2"));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Time.setOffset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequireProvider(value = EventStoreProvider.class, only = "map")
|
||||||
|
public void testEventsClearedOnRealmRemoval() {
|
||||||
|
// Create another realm
|
||||||
|
String newRealmId = inComittedTransaction(null, (session, t) -> {
|
||||||
|
RealmModel realm = session.realms().createRealm("events-realm");
|
||||||
|
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
|
||||||
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
|
Event e = createAuthEventForUser(realm, "u1");
|
||||||
|
eventStore.onEvent(e);
|
||||||
|
|
||||||
|
AdminEvent ae = new AdminEvent();
|
||||||
|
ae.setRealmId(realm.getId());
|
||||||
|
eventStore.onEvent(ae, false);
|
||||||
|
|
||||||
|
return realm.getId();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if events were created
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
|
assertThat(eventStore.createQuery().getResultStream().count(), is(1L));
|
||||||
|
assertThat(eventStore.createAdminQuery().getResultStream().count(), is(1L));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove realm
|
||||||
|
inComittedTransaction((Consumer<KeycloakSession>) session -> session.realms().removeRealm(newRealmId));
|
||||||
|
|
||||||
|
// Check events were removed
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
|
assertThat(eventStore.createQuery().getResultStream().count(), is(0L));
|
||||||
|
assertThat(eventStore.createAdminQuery().getResultStream().count(), is(0L));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DummyClientConnection implements ClientConnection {
|
private static class DummyClientConnection implements ClientConnection {
|
||||||
|
|
||||||
|
private static DummyClientConnection DUMMY_CONNECTION = new DummyClientConnection();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRemoteAddr() {
|
public String getRemoteAddr() {
|
||||||
return "remoteAddr";
|
return "remoteAddr";
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSet;
|
||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
import org.junit.runners.model.Statement;
|
import org.junit.runners.model.Statement;
|
||||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||||
|
import org.keycloak.events.EventStoreSpi;
|
||||||
import org.keycloak.models.DeploymentStateSpi;
|
import org.keycloak.models.DeploymentStateSpi;
|
||||||
import org.keycloak.models.UserLoginFailureSpi;
|
import org.keycloak.models.UserLoginFailureSpi;
|
||||||
import org.keycloak.models.UserSessionSpi;
|
import org.keycloak.models.UserSessionSpi;
|
||||||
|
@ -28,6 +29,7 @@ import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderF
|
||||||
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
||||||
import org.keycloak.models.map.client.MapClientProviderFactory;
|
import org.keycloak.models.map.client.MapClientProviderFactory;
|
||||||
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
||||||
|
import org.keycloak.models.map.events.MapEventStoreProviderFactory;
|
||||||
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
|
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
|
||||||
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory;
|
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory;
|
||||||
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionSpi;
|
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionSpi;
|
||||||
|
@ -84,7 +86,9 @@ public class HotRodMapStorage extends KeycloakModelParameters {
|
||||||
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config("storage-user-sessions.provider", HotRodMapStorageProviderFactory.PROVIDER_ID)
|
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config("storage-user-sessions.provider", HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.config("storage-client-sessions.provider", HotRodMapStorageProviderFactory.PROVIDER_ID)
|
.config("storage-client-sessions.provider", HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
.spi(EventStoreSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config("storage-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
.config("storage-auth-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
||||||
|
|
||||||
cf.spi(MapStorageSpi.NAME)
|
cf.spi(MapStorageSpi.NAME)
|
||||||
.provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
import org.junit.runners.model.Statement;
|
import org.junit.runners.model.Statement;
|
||||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||||
|
import org.keycloak.events.EventStoreSpi;
|
||||||
import org.keycloak.models.DeploymentStateSpi;
|
import org.keycloak.models.DeploymentStateSpi;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
@ -101,7 +102,9 @@ public class LdapMapStorage extends KeycloakModelParameters {
|
||||||
.spi(UserSessionSpi.NAME).config("map.storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi(UserSessionSpi.NAME).config("map.storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi(UserLoginFailureSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi(UserLoginFailureSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("authorizationPersister").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi("authorizationPersister").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("authenticationSessions").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
.spi("authenticationSessions").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
.spi(EventStoreSpi.NAME).config("map.storage-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
.spi(EventStoreSpi.NAME).config("map.storage-auth-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
package org.keycloak.testsuite.model.parameters;
|
package org.keycloak.testsuite.model.parameters;
|
||||||
|
|
||||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||||
|
import org.keycloak.events.EventStoreSpi;
|
||||||
import org.keycloak.models.DeploymentStateSpi;
|
import org.keycloak.models.DeploymentStateSpi;
|
||||||
import org.keycloak.models.UserLoginFailureSpi;
|
import org.keycloak.models.UserLoginFailureSpi;
|
||||||
import org.keycloak.models.UserSessionSpi;
|
import org.keycloak.models.UserSessionSpi;
|
||||||
import org.keycloak.models.dblock.NoLockingDBLockProviderFactory;
|
import org.keycloak.models.dblock.NoLockingDBLockProviderFactory;
|
||||||
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory;
|
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory;
|
||||||
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory;
|
||||||
|
import org.keycloak.models.map.events.MapEventStoreProviderFactory;
|
||||||
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
||||||
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||||
import org.keycloak.sessions.AuthenticationSessionSpi;
|
import org.keycloak.sessions.AuthenticationSessionSpi;
|
||||||
|
@ -33,7 +35,6 @@ import org.keycloak.models.map.group.MapGroupProviderFactory;
|
||||||
import org.keycloak.models.map.realm.MapRealmProviderFactory;
|
import org.keycloak.models.map.realm.MapRealmProviderFactory;
|
||||||
import org.keycloak.models.map.role.MapRoleProviderFactory;
|
import org.keycloak.models.map.role.MapRoleProviderFactory;
|
||||||
import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory;
|
import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory;
|
||||||
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
|
||||||
import org.keycloak.models.map.storage.MapStorageSpi;
|
import org.keycloak.models.map.storage.MapStorageSpi;
|
||||||
import org.keycloak.models.map.user.MapUserProviderFactory;
|
import org.keycloak.models.map.user.MapUserProviderFactory;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
@ -67,6 +68,7 @@ public class Map extends KeycloakModelParameters {
|
||||||
.add(MapUserSessionProviderFactory.class)
|
.add(MapUserSessionProviderFactory.class)
|
||||||
.add(MapUserLoginFailureProviderFactory.class)
|
.add(MapUserLoginFailureProviderFactory.class)
|
||||||
.add(NoLockingDBLockProviderFactory.class)
|
.add(NoLockingDBLockProviderFactory.class)
|
||||||
|
.add(MapEventStoreProviderFactory.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public Map() {
|
public Map() {
|
||||||
|
@ -87,6 +89,7 @@ public class Map extends KeycloakModelParameters {
|
||||||
.spi(UserSessionSpi.NAME).defaultProvider(MapUserSessionProviderFactory.PROVIDER_ID)
|
.spi(UserSessionSpi.NAME).defaultProvider(MapUserSessionProviderFactory.PROVIDER_ID)
|
||||||
.spi(UserLoginFailureSpi.NAME).defaultProvider(MapUserLoginFailureProviderFactory.PROVIDER_ID)
|
.spi(UserLoginFailureSpi.NAME).defaultProvider(MapUserLoginFailureProviderFactory.PROVIDER_ID)
|
||||||
.spi("dblock").defaultProvider(NoLockingDBLockProviderFactory.PROVIDER_ID)
|
.spi("dblock").defaultProvider(NoLockingDBLockProviderFactory.PROVIDER_ID)
|
||||||
|
.spi(EventStoreSpi.NAME).defaultProvider(MapEventStoreProviderFactory.PROVIDER_ID)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue