Skip to content

Commit

Permalink
Add HotRod no downtime store for events
Browse files Browse the repository at this point in the history
  • Loading branch information
mhajas authored and hmlnarik committed Jun 2, 2022
1 parent 0a8e132 commit 09c0a69
Show file tree
Hide file tree
Showing 18 changed files with 663 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.keycloak.authorization.model.Scope;
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
Expand All @@ -47,6 +49,8 @@
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.StringKeyConverter;
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.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
Expand Down Expand Up @@ -77,6 +81,10 @@
import org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
import org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntity;
import org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntityDelegate;
import org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntity;
import org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntityDelegate;
import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity;
import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntityDelegate;
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
Expand Down Expand Up @@ -180,6 +188,9 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
.constructor(MapPolicyEntity.class, HotRodPolicyEntityDelegate::new)
.constructor(MapPermissionTicketEntity.class, HotRodPermissionTicketEntityDelegate::new)

.constructor(MapAuthEventEntity.class, HotRodAuthEventEntityDelegate::new)
.constructor(MapAdminEventEntity.class, HotRodAdminEventEntityDelegate::new)

.build();

public static final Map<Class<?>, HotRodEntityDescriptor<?, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
Expand Down Expand Up @@ -293,6 +304,17 @@ public String getCacheName() {
return "authz";
}
});

// Events
ENTITY_DESCRIPTOR_MAP.put(Event.class,
new HotRodEntityDescriptor<>(Event.class,
HotRodAuthEventEntity.class,
HotRodAuthEventEntityDelegate::new));

ENTITY_DESCRIPTOR_MAP.put(AdminEvent.class,
new HotRodEntityDescriptor<>(AdminEvent.class,
HotRodAdminEventEntity.class,
HotRodAdminEventEntityDelegate::new));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.events.Event;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
Expand All @@ -36,6 +37,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

Expand All @@ -49,10 +51,10 @@ public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M
private final Class<E> hotRodEntityClass;
private final StringBuilder whereClauseBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
private final Map<String, Object> parameters;
private static final Pattern LIKE_PATTERN_DELIMITER = Pattern.compile("%+");
private static final Pattern NON_ANALYZED_FIELD_REGEX = Pattern.compile("[%_\\\\]");
// private static final Pattern ANALYZED_FIELD_REGEX = Pattern.compile("[+!^\"~*?:\\\\]"); // TODO reevaluate once https://github.com/keycloak/keycloak/issues/9295 is fixed
private static final Pattern ANALYZED_FIELD_REGEX = Pattern.compile("\\\\"); // escape "\" with extra "\"
private static final Pattern SINGLE_PERCENT_CHARACTER = Pattern.compile("^%+$");
public static final Map<SearchableModelField<?>, String> INFINISPAN_NAME_OVERRIDES = new HashMap<>();
public static final Set<SearchableModelField<?>> ANALYZED_MODEL_FIELDS = new HashSet<>();

Expand Down Expand Up @@ -87,6 +89,8 @@ public class IckleQueryMapModelCriteriaBuilder<E extends AbstractHotRodEntity, M
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.SCOPE_ID, "scopeIds");
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.ASSOCIATED_POLICY_ID, "associatedPolicyIds");
INFINISPAN_NAME_OVERRIDES.put(Policy.SearchableFields.CONFIG, "configs");

INFINISPAN_NAME_OVERRIDES.put(Event.SearchableFields.EVENT_TYPE, "type");
}

static {
Expand Down Expand Up @@ -223,48 +227,41 @@ private StringBuilder getWhereClauseBuilder() {
return whereClauseBuilder;
}

public static Object sanitize(Object value) {
public static Object sanitizeNonAnalyzed(Object value) {
if (value instanceof String) {
String sValue = (String) value;

if(SINGLE_PERCENT_CHARACTER.matcher(sValue).matches()) {
return value;
}

boolean anyBeginning = sValue.startsWith("%");
boolean anyEnd = sValue.endsWith("%");

String sanitizedString = NON_ANALYZED_FIELD_REGEX.matcher(sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0)))
.replaceAll("\\\\\\\\" + "$0");

return (anyBeginning ? "%" : "") + sanitizedString + (anyEnd ? "%" : "");
return sanitizeEachUnitAndReplaceDelimiter((String) value, IckleQueryMapModelCriteriaBuilder::sanitizeSingleUnitNonAnalyzed, "%");
}

return value;
}

public static Object sanitizeAnalyzed(Object value) {
if (value instanceof String) {
String sValue = (String) value;
boolean anyBeginning = sValue.startsWith("%");
boolean anyEnd = sValue.endsWith("%");
return sanitizeEachUnitAndReplaceDelimiter((String) value, IckleQueryMapModelCriteriaBuilder::sanitizeSingleUnitAnalyzed, "*");
}

if(SINGLE_PERCENT_CHARACTER.matcher(sValue).matches()) {
return "*";
}
return value;
}

String sanitizedString = ANALYZED_FIELD_REGEX.matcher(sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0)))
.replaceAll("\\\\\\\\"); // escape "\" with extra "\"
// .replaceAll("\\\\\\\\" + "$0"); skipped for now because Infinispan is not able to escape
// special characters for analyzed fields
// TODO reevaluate once https://github.com/keycloak/keycloak/issues/9295 is fixed
private static String sanitizeEachUnitAndReplaceDelimiter(String value, UnaryOperator<String> sanitizeSingleUnit, String replacement) {
return LIKE_PATTERN_DELIMITER.splitAsStream(value)
.map(sanitizeSingleUnit)
.collect(Collectors.joining(replacement))
+ (value.endsWith("%") ? replacement : "");
}

return (anyBeginning ? "*" : "") + sanitizedString + (anyEnd ? "*" : "");
}
private static String sanitizeSingleUnitNonAnalyzed(String value) {
return NON_ANALYZED_FIELD_REGEX.matcher(value).replaceAll("\\\\\\\\" + "$0");
}

return value;
private static String sanitizeSingleUnitAnalyzed(String value) {
return ANALYZED_FIELD_REGEX.matcher(value).replaceAll("\\\\\\\\"); // escape "\" with extra "\"
// .replaceAll("\\\\\\\\" + "$0"); skipped for now because Infinispan is not able to escape
// special characters for analyzed fields
// TODO reevaluate once https://github.com/keycloak/keycloak/issues/9295 is fixed
}


public static boolean isAnalyzedModelField(SearchableModelField<?> modelField) {
return ANALYZED_MODEL_FIELDS.contains(modelField);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ private static String notExists(String modelFieldName, Object[] values, Map<Stri
}

private static String iLike(String modelFieldName, Object[] values, Map<String, Object> parameters) {
String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitize(values[0]);
String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitizeNonAnalyzed(values[0]);
return singleValueOperator(ModelCriteriaBuilder.Operator.ILIKE)
.combine(modelFieldName + "Lowercase", new String[] {sanitizedValue.toLowerCase()}, parameters);
}

private static String like(String modelFieldName, Object[] values, Map<String, Object> parameters) {
String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitize(values[0]);
String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitizeNonAnalyzed(values[0]);
return singleValueOperator(ModelCriteriaBuilder.Operator.LIKE)
.combine(modelFieldName, new String[] {sanitizedValue}, parameters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,25 @@
package org.keycloak.models.map.storage.hotRod;

import org.keycloak.authorization.model.Policy;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils;
import org.keycloak.storage.SearchableModelField;
import org.keycloak.storage.StorageId;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.getFieldName;
import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.sanitizeAnalyzed;
Expand Down Expand Up @@ -58,6 +66,8 @@ public class IckleQueryWhereClauses {
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, IckleQueryWhereClauses::whereClauseForConsentClientFederationLink);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, IckleQueryWhereClauses::whereClauseForCorrespondingSessionId);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(Policy.SearchableFields.CONFIG, IckleQueryWhereClauses::whereClauseForPolicyConfig);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(Event.SearchableFields.EVENT_TYPE, IckleQueryWhereClauses::whereClauseForEventType);
WHERE_CLAUSE_PRODUCER_OVERRIDES.put(AdminEvent.SearchableFields.OPERATION_TYPE, IckleQueryWhereClauses::whereClauseForOperationType);
}

@FunctionalInterface
Expand Down Expand Up @@ -204,4 +214,31 @@ private static String whereClauseForPolicyConfig(String modelFieldName, ModelCri
String valueClause = IckleQueryOperators.combineExpressions(op, modelFieldName + ".value", realValues, parameters);
return "(" + nameClause + ")" + " AND " + "(" + valueClause + ")";
}

private static String whereClauseForEventType(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (values != null && values.length == 1) {
if (values[0] instanceof EventType) {
values[0] = HotRodTypesUtils.migrateEventTypeToHotRodEventType((EventType) values[0]);
} else if (values[0] instanceof Collection) {
values[0] = ((Collection<EventType>) values[0]).stream().map(HotRodTypesUtils::migrateEventTypeToHotRodEventType).collect(Collectors.toSet());
} else if (values[0] instanceof Stream) {
values[0] = ((Stream<EventType>) values[0]).map(HotRodTypesUtils::migrateEventTypeToHotRodEventType);
}
}

return produceWhereClause(modelFieldName, op, values, parameters);
}

private static String whereClauseForOperationType(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map<String, Object> parameters) {
if (values != null && values.length == 1) {
if (values[0] instanceof OperationType) {
values[0] = HotRodTypesUtils.migrateOperationTypeToHotRodOperationType((OperationType) values[0]);
} else if (values[0] instanceof Collection) {
values[0] = ((Collection<OperationType>) values[0]).stream().map(HotRodTypesUtils::migrateOperationTypeToHotRodOperationType).collect(Collectors.toSet());
} else if (values[0] instanceof Stream) {
values[0] = ((Stream<OperationType>) values[0]).map(HotRodTypesUtils::migrateOperationTypeToHotRodOperationType);
}
}
return produceWhereClause(modelFieldName, op, values, parameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@

package org.keycloak.models.map.storage.hotRod.common;

import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodDecisionStrategy;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodLogic;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodPolicyEnforcementMode;
import org.keycloak.models.map.storage.hotRod.events.HotRodEventType;
import org.keycloak.models.map.storage.hotRod.events.HotRodOperationType;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodLocalizationTexts;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequirement;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
Expand Down Expand Up @@ -195,4 +199,20 @@ public static HotRodLogic migrateLogicToHotRodLogic(Logic p0) {
public static Logic migrateHotRodLogicToLogic(HotRodLogic p0) {
return p0 == null ? null : Logic.values()[p0.ordinal()];
}

public static OperationType migrateHotRodOperationTypeToOperationType(HotRodOperationType p0) {
return p0 == null ? null : OperationType.values()[p0.ordinal()];
}

public static HotRodOperationType migrateOperationTypeToHotRodOperationType(OperationType p0) {
return p0 == null ? null : HotRodOperationType.values()[p0.ordinal()];
}

public static HotRodEventType migrateEventTypeToHotRodEventType(EventType p0) {
return p0 == null ? null : HotRodEventType.values()[p0.ordinal()];
}

public static EventType migrateHotRodEventTypeToEventType(HotRodEventType p0) {
return p0 == null ? null : EventType.values()[p0.ordinal()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity;
import org.keycloak.models.map.storage.hotRod.events.HotRodAdminEventEntity;
import org.keycloak.models.map.storage.hotRod.events.HotRodAuthEventEntity;
import org.keycloak.models.map.storage.hotRod.events.HotRodEventType;
import org.keycloak.models.map.storage.hotRod.events.HotRodOperationType;
import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity;
import org.keycloak.models.map.storage.hotRod.realm.HotRodRealmEntity;
Expand Down Expand Up @@ -123,6 +127,12 @@
HotRodLogic.class,
HotRodPolicyEnforcementMode.class,

// Events
HotRodAuthEventEntity.class,
HotRodEventType.class,
HotRodAdminEventEntity.class,
HotRodOperationType.class,

// Common
HotRodPair.class,
HotRodStringPair.class,
Expand Down
Loading

0 comments on commit 09c0a69

Please sign in to comment.