KEYCLOAK-17696 Make MapStorageFactory amphibian

This commit is contained in:
Hynek Mlnarik 2021-02-26 11:45:38 +01:00 committed by Hynek Mlnařík
parent e46a5484c5
commit 6d97a573e6
107 changed files with 4834 additions and 4743 deletions

View file

@ -30,6 +30,6 @@ embed-server
/subsystem=keycloak-server/spi=userSessions:add(default-provider=map)
/subsystem=keycloak-server/spi=mapStorage:add(default-provider=concurrenthashmap)
/subsystem=keycloak-server/spi=mapStorage/provider=concurrenthashmap:add(properties={dir="${jboss.server.data.dir}/map"},enabled=true)
/subsystem=keycloak-server/spi=mapStorage/provider=concurrenthashmap:add(properties={dir="${jboss.server.data.dir}/map",keyType.realms=string,keyType.authz-resource-servers=string},enabled=true)
quit

View file

@ -276,6 +276,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.map(entity -> this.wrap(realm, entity, offline));
}
@Override
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline) {
return getClientSession(userSession, client, clientSessionId == null ? null : UUID.fromString(clientSessionId), offline);
}
@Override
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline) {
AuthenticatedClientSessionEntity entity = getClientSessionEntity(clientSessionId, offline);

View file

@ -136,4 +136,9 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
provider.getLoginFailuresTx().addTask(key, task);
}
@Override
public String getId() {
return key.toString();
}
}

View file

@ -1,104 +0,0 @@
/*
* Copyright 2020 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.authSession;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractRootAuthenticationSessionEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private int timestamp;
private Map<String, MapAuthenticationSessionEntity> authenticationSessions = new ConcurrentHashMap<>();
protected AbstractRootAuthenticationSessionEntity() {
this.id = null;
this.realmId = null;
}
public AbstractRootAuthenticationSessionEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.updated |= !Objects.equals(this.timestamp, timestamp);
this.timestamp = timestamp;
}
public Map<String, MapAuthenticationSessionEntity> getAuthenticationSessions() {
return authenticationSessions;
}
public void setAuthenticationSessions(Map<String, MapAuthenticationSessionEntity> authenticationSessions) {
this.updated |= !Objects.equals(this.authenticationSessions, authenticationSessions);
this.authenticationSessions = authenticationSessions;
}
public MapAuthenticationSessionEntity removeAuthenticationSession(String tabId) {
MapAuthenticationSessionEntity entity = this.authenticationSessions.remove(tabId);
this.updated |= entity != null;
return entity;
}
public void addAuthenticationSession(String tabId, MapAuthenticationSessionEntity entity) {
this.updated |= !Objects.equals(this.authenticationSessions.put(tabId, entity), entity);
}
public void clearAuthenticationSessions() {
this.updated |= !this.authenticationSessions.isEmpty();
this.authenticationSessions.clear();
}
}

View file

@ -37,7 +37,7 @@ public class MapAuthenticationSessionAdapter implements AuthenticationSessionMod
private final KeycloakSession session;
private final MapRootAuthenticationSessionAdapter parent;
private final String tabId;
private MapAuthenticationSessionEntity entity;
private final MapAuthenticationSessionEntity entity;
public MapAuthenticationSessionAdapter(KeycloakSession session, MapRootAuthenticationSessionAdapter parent,
String tabId, MapAuthenticationSessionEntity entity) {

View file

@ -81,6 +81,6 @@ public class MapAuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent
@Override
public String toString() {
return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, tabId=%s, clientUUID=%s, authNotesFragment=%s ]",
authSessionId, clientUUID, authNotesFragment);
authSessionId, tabId, clientUUID, authNotesFragment);
}
}

View file

@ -31,17 +31,12 @@ import java.util.stream.Collectors;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapRootAuthenticationSessionAdapter extends AbstractRootAuthenticationSessionModel<MapRootAuthenticationSessionEntity> {
public abstract class MapRootAuthenticationSessionAdapter<K> extends AbstractRootAuthenticationSessionModel<MapRootAuthenticationSessionEntity<K>> {
public MapRootAuthenticationSessionAdapter(KeycloakSession session, RealmModel realm, MapRootAuthenticationSessionEntity entity) {
public MapRootAuthenticationSessionAdapter(KeycloakSession session, RealmModel realm, MapRootAuthenticationSessionEntity<K> entity) {
super(session, realm, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public RealmModel getRealm() {
return session.realms().getRealm(entity.getRealmId());
@ -102,9 +97,7 @@ public class MapRootAuthenticationSessionAdapter extends AbstractRootAuthenticat
public void removeAuthenticationSessionByTabId(String tabId) {
if (entity.removeAuthenticationSession(tabId) != null) {
if (entity.getAuthenticationSessions().isEmpty()) {
MapRootAuthenticationSessionProvider authenticationSessionProvider =
(MapRootAuthenticationSessionProvider) session.authenticationSessions();
authenticationSessionProvider.tx.delete(entity.getId());
session.authenticationSessions().removeRootAuthenticationSession(realm, this);
} else {
entity.setTimestamp(Time.currentTime());
}

View file

@ -16,18 +16,89 @@
*/
package org.keycloak.models.map.authSession;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapRootAuthenticationSessionEntity extends AbstractRootAuthenticationSessionEntity<UUID> {
public class MapRootAuthenticationSessionEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private int timestamp;
private Map<String, MapAuthenticationSessionEntity> authenticationSessions = new ConcurrentHashMap<>();
protected MapRootAuthenticationSessionEntity() {
super();
this.id = null;
this.realmId = null;
}
public MapRootAuthenticationSessionEntity(UUID id, String realmId) {
super(id, realmId);
public MapRootAuthenticationSessionEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.updated |= !Objects.equals(this.timestamp, timestamp);
this.timestamp = timestamp;
}
public Map<String, MapAuthenticationSessionEntity> getAuthenticationSessions() {
return authenticationSessions;
}
public void setAuthenticationSessions(Map<String, MapAuthenticationSessionEntity> authenticationSessions) {
this.updated |= !Objects.equals(this.authenticationSessions, authenticationSessions);
this.authenticationSessions = authenticationSessions;
}
public MapAuthenticationSessionEntity removeAuthenticationSession(String tabId) {
MapAuthenticationSessionEntity entity = this.authenticationSessions.remove(tabId);
this.updated |= entity != null;
return entity;
}
public void addAuthenticationSession(String tabId, MapAuthenticationSessionEntity entity) {
this.updated |= !Objects.equals(this.authenticationSessions.put(tabId, entity), entity);
}
public void clearAuthenticationSessions() {
this.updated |= !this.authenticationSessions.isEmpty();
this.authenticationSessions.clear();
}
}

View file

@ -36,7 +36,6 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
@ -45,17 +44,16 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapRootAuthenticationSessionProvider implements AuthenticationSessionProvider {
public class MapRootAuthenticationSessionProvider<K> implements AuthenticationSessionProvider {
private static final Logger LOG = Logger.getLogger(MapRootAuthenticationSessionProvider.class);
private final KeycloakSession session;
protected final MapKeycloakTransaction<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> tx;
private final MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> sessionStore;
protected final MapKeycloakTransaction<K, MapRootAuthenticationSessionEntity<K>, RootAuthenticationSessionModel> tx;
private final MapStorage<K, MapRootAuthenticationSessionEntity<K>, RootAuthenticationSessionModel> sessionStore;
private static final Predicate<MapRootAuthenticationSessionEntity> ALWAYS_FALSE = role -> false;
private static final String AUTHENTICATION_SESSION_EVENTS = "AUTHENTICATION_SESSION_EVENTS";
public MapRootAuthenticationSessionProvider(KeycloakSession session, MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> sessionStore) {
public MapRootAuthenticationSessionProvider(KeycloakSession session, MapStorage<K, MapRootAuthenticationSessionEntity<K>, RootAuthenticationSessionModel> sessionStore) {
this.session = session;
this.sessionStore = sessionStore;
this.tx = sessionStore.createTransaction(session);
@ -63,21 +61,26 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
session.getTransactionManager().enlistAfterCompletion(tx);
}
private Function<MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> entityToAdapterFunc(RealmModel realm) {
private Function<MapRootAuthenticationSessionEntity<K>, RootAuthenticationSessionModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapRootAuthenticationSessionAdapter(session, realm, registerEntityForChanges(origEntity));
return origEntity -> new MapRootAuthenticationSessionAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return sessionStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private MapRootAuthenticationSessionEntity registerEntityForChanges(MapRootAuthenticationSessionEntity origEntity) {
MapRootAuthenticationSessionEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapRootAuthenticationSessionEntity::isUpdated);
private MapRootAuthenticationSessionEntity<K> registerEntityForChanges(MapRootAuthenticationSessionEntity<K> origEntity) {
MapRootAuthenticationSessionEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapRootAuthenticationSessionEntity<K>::isUpdated);
return res;
}
private Predicate<MapRootAuthenticationSessionEntity> entityRealmFilter(String realmId) {
private Predicate<MapRootAuthenticationSessionEntity<K>> entityRealmFilter(String realmId) {
if (realmId == null) {
return MapRootAuthenticationSessionProvider.ALWAYS_FALSE;
return c -> false;
}
return entity -> Objects.equals(realmId, entity.getRealmId());
}
@ -92,12 +95,12 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
public RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm, String id) {
Objects.requireNonNull(realm, "The provided realm can't be null!");
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? sessionStore.getKeyConvertor().yieldNewUniqueKey() : sessionStore.getKeyConvertor().fromString(id);
LOG.tracef("createRootAuthenticationSession(%s)%s", realm.getName(), getShortStackTrace());
// create map authentication session entity
MapRootAuthenticationSessionEntity entity = new MapRootAuthenticationSessionEntity(entityId, realm.getId());
MapRootAuthenticationSessionEntity<K> entity = new MapRootAuthenticationSessionEntity<>(entityId, realm.getId());
entity.setRealmId(realm.getId());
entity.setTimestamp(Time.currentTime());
@ -119,7 +122,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
LOG.tracef("getRootAuthenticationSession(%s, %s)%s", realm.getName(), authenticationSessionId, getShortStackTrace());
MapRootAuthenticationSessionEntity entity = tx.read(UUID.fromString(authenticationSessionId));
MapRootAuthenticationSessionEntity<K> entity = tx.read(sessionStore.getKeyConvertor().fromStringSafe(authenticationSessionId));
return (entity == null || !entityRealmFilter(realm.getId()).test(entity))
? null
: entityToAdapterFunc(realm).apply(entity);
@ -128,7 +131,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
@Override
public void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession) {
Objects.requireNonNull(authenticationSession, "The provided root authentication session can't be null!");
tx.delete(UUID.fromString(authenticationSession.getId()));
tx.delete(sessionStore.getKeyConvertor().fromString(authenticationSession.getId()));
}
@Override
@ -147,7 +150,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.TIMESTAMP, Operator.LT, expired);
long deletedCount = tx.delete(UUID.randomUUID(), mcb);
long deletedCount = tx.delete(sessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
LOG.debugf("Removed %d expired authentication sessions for realm '%s'", deletedCount, realm.getName());
}

View file

@ -17,33 +17,30 @@
package org.keycloak.models.map.authSession;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
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.sessions.AuthenticationSessionProvider;
import org.keycloak.sessions.AuthenticationSessionProviderFactory;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.UUID;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapRootAuthenticationSessionProviderFactory extends AbstractMapProviderFactory<AuthenticationSessionProvider>
public class MapRootAuthenticationSessionProviderFactory<K> extends AbstractMapProviderFactory<AuthenticationSessionProvider, K, MapRootAuthenticationSessionEntity<K>, RootAuthenticationSessionModel>
implements AuthenticationSessionProviderFactory {
private MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> store;
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("sessions", UUID.class, MapRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class);
public MapRootAuthenticationSessionProviderFactory() {
super(MapRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class);
}
@Override
public AuthenticationSessionProvider create(KeycloakSession session) {
return new MapRootAuthenticationSessionProvider(session, store);
return new MapRootAuthenticationSessionProvider<>(session, getStorage(session));
}
@Override
public String getHelpText() {
return "Authentication session provider";
}
}

View file

@ -18,11 +18,6 @@
package org.keycloak.models.map.authorization;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.PermissionTicketStore;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceServerStore;
@ -30,14 +25,8 @@ import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.storage.MapStorage;
import java.util.UUID;
/**
* @author mhajas
@ -51,7 +40,7 @@ public class MapAuthorizationStore implements StoreFactory {
private final PermissionTicketStore permissionTicketStore;
private boolean readOnly;
public MapAuthorizationStore(KeycloakSession session, MapStorage<UUID, MapPermissionTicketEntity, PermissionTicket> permissionTicketStore, MapStorage<UUID, MapPolicyEntity, Policy> policyStore, MapStorage<String, MapResourceServerEntity, ResourceServer> resourceServerStore, MapStorage<UUID, MapResourceEntity, Resource> resourceStore, MapStorage<UUID, MapScopeEntity, Scope> scopeStore, AuthorizationProvider provider) {
public MapAuthorizationStore(KeycloakSession session, MapStorage permissionTicketStore, MapStorage policyStore, MapStorage resourceServerStore, MapStorage resourceStore, MapStorage scopeStore, AuthorizationProvider provider) {
this.permissionTicketStore = new MapPermissionTicketStore(session, permissionTicketStore, provider);
this.policyStore = new MapPolicyStore(session, policyStore, provider);
this.resourceServerStore = new MapResourceServerStore(session, resourceServerStore, provider);

View file

@ -26,32 +26,49 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.AuthorizationStoreFactory;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
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 java.util.UUID;
import org.keycloak.models.map.storage.MapStorageSpi;
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
/**
* @author mhajas
*/
public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory {
public class MapAuthorizationStoreFactory<K> implements AmphibianProviderFactory<StoreFactory>, AuthorizationStoreFactory {
private MapStorage<UUID, MapPermissionTicketEntity, PermissionTicket> permissionTicketStore;
private MapStorage<UUID, MapPolicyEntity, Policy> policyStore;
private MapStorage<String, MapResourceServerEntity, ResourceServer> resourceServerStore;
private MapStorage<UUID, MapResourceEntity, Resource> resourceStore;
private MapStorage<UUID, MapScopeEntity, Scope> scopeStore;
public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID;
private Config.Scope storageConfigScope;
@Override
public StoreFactory create(KeycloakSession session) {
MapStorageProviderFactory storageProviderFactory = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
MapStorageProvider.class, storageConfigScope, MapStorageSpi.NAME);
final MapStorageProvider mapStorageProvider = storageProviderFactory.create(session);
AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
MapStorage permissionTicketStore;
MapStorage policyStore;
MapStorage resourceServerStore;
MapStorage resourceStore;
MapStorage scopeStore;
permissionTicketStore = mapStorageProvider.getStorage(MapPermissionTicketEntity.class, PermissionTicket.class);
policyStore = mapStorageProvider.getStorage(MapPolicyEntity.class, Policy.class);
resourceServerStore = mapStorageProvider.getStorage(MapResourceServerEntity.class, ResourceServer.class);
resourceStore = mapStorageProvider.getStorage(MapResourceEntity.class, Resource.class);
scopeStore = mapStorageProvider.getStorage(MapScopeEntity.class, Scope.class);
return new MapAuthorizationStore(session,
permissionTicketStore,
policyStore,
@ -63,21 +80,8 @@ public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory {
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
AuthorizationStoreFactory.super.postInit(factory);
MapStorageProviderFactory mapStorageProvider = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
permissionTicketStore = mapStorageProvider.getStorage("authzPermissionTickets", UUID.class, MapPermissionTicketEntity.class, PermissionTicket.class);
policyStore = mapStorageProvider.getStorage("authzPolicies", UUID.class, MapPolicyEntity.class, Policy.class);
resourceServerStore = mapStorageProvider.getStorage("authzResourceServers", String.class, MapResourceServerEntity.class, ResourceServer.class);
resourceStore = mapStorageProvider.getStorage("authzResources", UUID.class, MapResourceEntity.class, Resource.class);
scopeStore = mapStorageProvider.getStorage("authzScopes", UUID.class, MapScopeEntity.class, Scope.class);
public void init(org.keycloak.Config.Scope config) {
this.storageConfigScope = config.scope("storage");
}
@Override
@ -87,6 +91,11 @@ public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory {
@Override
public String getId() {
return "map";
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return "Authorization store provider";
}
}

View file

@ -28,7 +28,6 @@ import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.authorization.adapter.MapPermissionTicketAdapter;
import org.keycloak.models.map.authorization.entity.AbstractPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.common.Serialization;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
@ -37,11 +36,11 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -49,30 +48,35 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.StreamsUtil.distinctByKey;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapPermissionTicketStore implements PermissionTicketStore {
public class MapPermissionTicketStore<K extends Comparable<K>> implements PermissionTicketStore {
private static final Logger LOG = Logger.getLogger(MapPermissionTicketStore.class);
private final AuthorizationProvider authorizationProvider;
final MapKeycloakTransaction<UUID, MapPermissionTicketEntity, PermissionTicket> tx;
private final MapStorage<UUID, MapPermissionTicketEntity, PermissionTicket> permissionTicketStore;
final MapKeycloakTransaction<K, MapPermissionTicketEntity<K>, PermissionTicket> tx;
private final MapStorage<K, MapPermissionTicketEntity<K>, PermissionTicket> permissionTicketStore;
public MapPermissionTicketStore(KeycloakSession session, MapStorage<UUID, MapPermissionTicketEntity, PermissionTicket> permissionTicketStore, AuthorizationProvider provider) {
public MapPermissionTicketStore(KeycloakSession session, MapStorage<K, MapPermissionTicketEntity<K>, PermissionTicket> permissionTicketStore, AuthorizationProvider provider) {
this.authorizationProvider = provider;
this.permissionTicketStore = permissionTicketStore;
this.tx = permissionTicketStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private MapPermissionTicketEntity registerEntityForChanges(MapPermissionTicketEntity origEntity) {
final MapPermissionTicketEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapPermissionTicketEntity::isUpdated);
private MapPermissionTicketEntity<K> registerEntityForChanges(MapPermissionTicketEntity<K> origEntity) {
final MapPermissionTicketEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapPermissionTicketEntity<K>::isUpdated);
return res;
}
private PermissionTicket entityToAdapter(MapPermissionTicketEntity origEntity) {
private PermissionTicket entityToAdapter(MapPermissionTicketEntity<K> origEntity) {
if (origEntity == null) return null;
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return new MapPermissionTicketAdapter(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory());
return new MapPermissionTicketAdapter<K>(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory()) {
@Override
public String getId() {
return permissionTicketStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private ModelCriteriaBuilder<PermissionTicket> forResourceServer(String resourceServerId) {
@ -116,13 +120,14 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
+ ", Resource: " + resourceId + ", owner: " + owner + ", scopeId: " + scopeId + " already exists.");
}
MapPermissionTicketEntity entity = new MapPermissionTicketEntity(UUID.randomUUID());
entity.setResourceId(UUID.fromString(resourceId));
final K newId = permissionTicketStore.getKeyConvertor().yieldNewUniqueKey();
MapPermissionTicketEntity<K> entity = new MapPermissionTicketEntity<>(newId);
entity.setResourceId(resourceId);
entity.setRequester(requester);
entity.setCreatedTimestamp(System.currentTimeMillis());
if (scopeId != null) {
entity.setScopeId(UUID.fromString(scopeId));
entity.setScopeId(scopeId);
}
entity.setOwner(owner);
@ -136,7 +141,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
@Override
public void delete(String id) {
LOG.tracef("delete(%s)%s", id, getShortStackTrace());
tx.delete(UUID.fromString(id));
tx.delete(permissionTicketStore.getKeyConvertor().fromString(id));
}
@Override
@ -204,7 +209,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
if (r == null || r.isEmpty()) {
return Collections.emptyList();
}
mcb = mcb.compare(SearchableFields.RESOURCE_ID, Operator.IN, r.stream().map(Resource::getId).collect(Collectors.toList()));
mcb = mcb.compare(SearchableFields.RESOURCE_ID, Operator.IN, r.stream().map(Resource::getId));
}
mcb = mcb.and(
@ -213,8 +218,9 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
.toArray(ModelCriteriaBuilder[]::new)
);
Comparator<? super MapPermissionTicketEntity<K>> c = Comparator.comparing(MapPermissionTicketEntity::getId);
return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.sorted(MapPermissionTicketEntity.COMPARE_BY_ID), firstResult, maxResult)
.sorted(c), firstResult, maxResult)
.map(this::entityToAdapter)
.collect(Collectors.toList());
}
@ -278,14 +284,14 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
.compare(SearchableFields.REQUESTER, Operator.EQ, requester)
.compare(SearchableFields.GRANTED_TIMESTAMP, Operator.EXISTS);
Function<MapPermissionTicketEntity, Resource> ticketResourceMapper;
Function<MapPermissionTicketEntity<K>, Resource> ticketResourceMapper;
ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
if (name != null) {
ticketResourceMapper = ticket -> {
Map<Resource.FilterOption, String[]> filterOptionMap = new EnumMap<>(Resource.FilterOption.class);
filterOptionMap.put(Resource.FilterOption.ID, new String[] {ticket.getResourceId().toString()});
filterOptionMap.put(Resource.FilterOption.ID, new String[] {ticket.getResourceId()});
filterOptionMap.put(Resource.FilterOption.NAME, new String[] {name});
List<Resource> resource = resourceStore.findByResourceServer(filterOptionMap, ticket.getResourceServerId(), -1, 1);
@ -294,11 +300,11 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
};
} else {
ticketResourceMapper = ticket -> resourceStore
.findById(ticket.getResourceId().toString(), ticket.getResourceServerId());
.findById(ticket.getResourceId(), ticket.getResourceServerId());
}
return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.filter(distinctByKey(AbstractPermissionTicketEntity::getResourceId))
.filter(distinctByKey(MapPermissionTicketEntity::getResourceId))
.sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID)
.map(ticketResourceMapper)
.filter(Objects::nonNull), first, max)
@ -311,10 +317,10 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
.compare(SearchableFields.OWNER, Operator.EQ, owner);
return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.filter(distinctByKey(AbstractPermissionTicketEntity::getResourceId))
.filter(distinctByKey(MapPermissionTicketEntity::getResourceId))
.sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID), first, max)
.map(ticket -> authorizationProvider.getStoreFactory().getResourceStore()
.findById(ticket.getResourceId().toString(), ticket.getResourceServerId()))
.findById(ticket.getResourceId(), ticket.getResourceServerId()))
.collect(Collectors.toList());
}
}

View file

@ -26,7 +26,6 @@ import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.authorization.adapter.MapPolicyAdapter;
import org.keycloak.models.map.authorization.entity.AbstractPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.common.Serialization;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
@ -38,37 +37,41 @@ import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentati
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapPolicyStore implements PolicyStore {
public class MapPolicyStore<K> implements PolicyStore {
private static final Logger LOG = Logger.getLogger(MapPolicyStore.class);
private final AuthorizationProvider authorizationProvider;
final MapKeycloakTransaction<UUID, MapPolicyEntity, Policy> tx;
private final MapStorage<UUID, MapPolicyEntity, Policy> policyStore;
final MapKeycloakTransaction<K, MapPolicyEntity<K>, Policy> tx;
private final MapStorage<K, MapPolicyEntity<K>, Policy> policyStore;
public MapPolicyStore(KeycloakSession session, MapStorage<UUID, MapPolicyEntity, Policy> policyStore, AuthorizationProvider provider) {
public MapPolicyStore(KeycloakSession session, MapStorage<K, MapPolicyEntity<K>, Policy> policyStore, AuthorizationProvider provider) {
this.authorizationProvider = provider;
this.policyStore = policyStore;
this.tx = policyStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private MapPolicyEntity registerEntityForChanges(MapPolicyEntity origEntity) {
final MapPolicyEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapPolicyEntity::isUpdated);
private MapPolicyEntity<K> registerEntityForChanges(MapPolicyEntity<K> origEntity) {
final MapPolicyEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapPolicyEntity<K>::isUpdated);
return res;
}
private Policy entityToAdapter(MapPolicyEntity origEntity) {
private Policy entityToAdapter(MapPolicyEntity<K> origEntity) {
if (origEntity == null) return null;
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return new MapPolicyAdapter(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory());
return new MapPolicyAdapter<K>(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory()) {
@Override
public String getId() {
return policyStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private ModelCriteriaBuilder<Policy> forResourceServer(String resourceServerId) {
@ -92,8 +95,8 @@ public class MapPolicyStore implements PolicyStore {
throw new ModelDuplicateException("Policy with name '" + representation.getName() + "' for " + resourceServer.getId() + " already exists");
}
UUID uid = representation.getId() == null ? UUID.randomUUID() : UUID.fromString(representation.getId());
MapPolicyEntity entity = new MapPolicyEntity(uid);
K uid = representation.getId() == null ? policyStore.getKeyConvertor().yieldNewUniqueKey() : policyStore.getKeyConvertor().fromString(representation.getId());
MapPolicyEntity<K> entity = new MapPolicyEntity<>(uid);
entity.setType(representation.getType());
entity.setName(representation.getName());
entity.setResourceServerId(resourceServer.getId());
@ -106,7 +109,7 @@ public class MapPolicyStore implements PolicyStore {
@Override
public void delete(String id) {
LOG.tracef("delete(%s)%s", id, getShortStackTrace());
tx.delete(UUID.fromString(id));
tx.delete(policyStore.getKeyConvertor().fromString(id));
}
@Override
@ -155,9 +158,9 @@ public class MapPolicyStore implements PolicyStore {
}
return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.sorted(AbstractPolicyEntity.COMPARE_BY_NAME), firstResult, maxResult)
.map(MapPolicyEntity::getId)
.map(UUID::toString)
.sorted(MapPolicyEntity.COMPARE_BY_NAME), firstResult, maxResult)
.map(MapPolicyEntity<K>::getId)
.map(K::toString)
.map(id -> authorizationProvider.getStoreFactory().getPolicyStore().findById(id, resourceServerId)) // We need to go through cache
.collect(Collectors.toList());
}

View file

@ -41,30 +41,35 @@ import org.keycloak.storage.StorageId;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
public class MapResourceServerStore implements ResourceServerStore {
public class MapResourceServerStore<K> implements ResourceServerStore {
private static final Logger LOG = Logger.getLogger(MapResourceServerStore.class);
private final AuthorizationProvider authorizationProvider;
final MapKeycloakTransaction<String, MapResourceServerEntity, ResourceServer> tx;
private final MapStorage<String, MapResourceServerEntity, ResourceServer> resourceServerStore;
final MapKeycloakTransaction<K, MapResourceServerEntity<K>, ResourceServer> tx;
private final MapStorage<K, MapResourceServerEntity<K>, ResourceServer> resourceServerStore;
public MapResourceServerStore(KeycloakSession session, MapStorage<String, MapResourceServerEntity, ResourceServer> resourceServerStore, AuthorizationProvider provider) {
public MapResourceServerStore(KeycloakSession session, MapStorage<K, MapResourceServerEntity<K>, ResourceServer> resourceServerStore, AuthorizationProvider provider) {
this.resourceServerStore = resourceServerStore;
this.tx = resourceServerStore.createTransaction(session);
this.authorizationProvider = provider;
session.getTransactionManager().enlist(tx);
}
private MapResourceServerEntity registerEntityForChanges(MapResourceServerEntity origEntity) {
final MapResourceServerEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapResourceServerEntity::isUpdated);
private MapResourceServerEntity<K> registerEntityForChanges(MapResourceServerEntity<K> origEntity) {
final MapResourceServerEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapResourceServerEntity<K>::isUpdated);
return res;
}
private ResourceServer entityToAdapter(MapResourceServerEntity origEntity) {
private ResourceServer entityToAdapter(MapResourceServerEntity<K> origEntity) {
if (origEntity == null) return null;
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return new MapResourceServerAdapter(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory());
return new MapResourceServerAdapter<K>(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory()) {
@Override
public String getId() {
return resourceServerStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
@Override
@ -77,11 +82,11 @@ public class MapResourceServerStore implements ResourceServerStore {
throw new ModelException("Creating resource server from federated ClientModel not supported");
}
if (tx.read(clientId) != null) {
if (tx.read(resourceServerStore.getKeyConvertor().fromString(clientId)) != null) {
throw new ModelDuplicateException("Resource server already exists: " + clientId);
}
MapResourceServerEntity entity = new MapResourceServerEntity(clientId);
MapResourceServerEntity<K> entity = new MapResourceServerEntity<>(resourceServerStore.getKeyConvertor().fromString(clientId));
tx.create(entity.getId(), entity);
@ -113,7 +118,7 @@ public class MapResourceServerStore implements ResourceServerStore {
.map(Scope::getId)
.forEach(scopeStore::delete);
tx.delete(id);
tx.delete(resourceServerStore.getKeyConvertor().fromString(id));
}
@Override
@ -125,7 +130,7 @@ public class MapResourceServerStore implements ResourceServerStore {
}
MapResourceServerEntity entity = tx.read(id);
MapResourceServerEntity<K> entity = tx.read(resourceServerStore.getKeyConvertor().fromStringSafe(id));
return entityToAdapter(entity);
}
}

View file

@ -26,7 +26,6 @@ import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.authorization.adapter.MapResourceAdapter;
import org.keycloak.models.map.authorization.entity.AbstractResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.common.Serialization;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
@ -35,40 +34,45 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapResourceStore implements ResourceStore {
public class MapResourceStore<K extends Comparable<K>> implements ResourceStore {
private static final Logger LOG = Logger.getLogger(MapResourceStore.class);
private final AuthorizationProvider authorizationProvider;
final MapKeycloakTransaction<UUID, MapResourceEntity, Resource> tx;
private final MapStorage<UUID, MapResourceEntity, Resource> resourceStore;
final MapKeycloakTransaction<K, MapResourceEntity<K>, Resource> tx;
private final MapStorage<K, MapResourceEntity<K>, Resource> resourceStore;
public MapResourceStore(KeycloakSession session, MapStorage<UUID, MapResourceEntity, Resource> resourceStore, AuthorizationProvider provider) {
public MapResourceStore(KeycloakSession session, MapStorage<K, MapResourceEntity<K>, Resource> resourceStore, AuthorizationProvider provider) {
this.resourceStore = resourceStore;
this.tx = resourceStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
authorizationProvider = provider;
}
private MapResourceEntity registerEntityForChanges(MapResourceEntity origEntity) {
final MapResourceEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapResourceEntity::isUpdated);
private MapResourceEntity<K> registerEntityForChanges(MapResourceEntity<K> origEntity) {
final MapResourceEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapResourceEntity<K>::isUpdated);
return res;
}
private Resource entityToAdapter(MapResourceEntity origEntity) {
private Resource entityToAdapter(MapResourceEntity<K> origEntity) {
if (origEntity == null) return null;
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return new MapResourceAdapter(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory());
return new MapResourceAdapter<K>(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory()) {
@Override
public String getId() {
return resourceStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private ModelCriteriaBuilder<Resource> forResourceServer(String resourceServerId) {
@ -92,8 +96,8 @@ public class MapResourceStore implements ResourceStore {
throw new ModelDuplicateException("Resource with name '" + name + "' for " + resourceServer.getId() + " already exists for request owner " + owner);
}
UUID uid = id == null ? UUID.randomUUID() : UUID.fromString(id);
MapResourceEntity entity = new MapResourceEntity(uid);
K uid = id == null ? resourceStore.getKeyConvertor().yieldNewUniqueKey(): resourceStore.getKeyConvertor().fromString(id);
MapResourceEntity<K> entity = new MapResourceEntity<>(uid);
entity.setName(name);
entity.setResourceServerId(resourceServer.getId());
@ -108,7 +112,7 @@ public class MapResourceStore implements ResourceStore {
public void delete(String id) {
LOG.tracef("delete(%s)%s", id, getShortStackTrace());
tx.delete(UUID.fromString(id));
tx.delete(resourceStore.getKeyConvertor().fromString(id));
}
@Override
@ -129,9 +133,10 @@ public class MapResourceStore implements ResourceStore {
private void findByOwnerFilter(String ownerId, String resourceServerId, Consumer<Resource> consumer, int firstResult, int maxResult) {
LOG.tracef("findByOwnerFilter(%s, %s, %s, %d, %d)%s", ownerId, resourceServerId, consumer, firstResult, maxResult, getShortStackTrace());
Comparator<? super MapResourceEntity<K>> c = Comparator.comparing(MapResourceEntity::getId);
paginatedStream(tx.getUpdatedNotRemoved(forResourceServer(resourceServerId)
.compare(SearchableFields.OWNER, Operator.EQ, ownerId))
.sorted(MapResourceEntity.COMPARE_BY_ID), firstResult, maxResult)
.sorted(c), firstResult, maxResult)
.map(this::entityToAdapter)
.forEach(consumer);
}
@ -174,7 +179,7 @@ public class MapResourceStore implements ResourceStore {
);
return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.sorted(AbstractResourceEntity.COMPARE_BY_NAME), firstResult, maxResult)
.sorted(MapResourceEntity.COMPARE_BY_NAME), firstResult, maxResult)
.map(this::entityToAdapter)
.collect(Collectors.toList());
}

View file

@ -36,36 +36,40 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapScopeStore implements ScopeStore {
public class MapScopeStore<K> implements ScopeStore {
private static final Logger LOG = Logger.getLogger(MapScopeStore.class);
private final AuthorizationProvider authorizationProvider;
final MapKeycloakTransaction<UUID, MapScopeEntity, Scope> tx;
private final MapStorage<UUID, MapScopeEntity, Scope> scopeStore;
final MapKeycloakTransaction<K, MapScopeEntity<K>, Scope> tx;
private final MapStorage<K, MapScopeEntity<K>, Scope> scopeStore;
public MapScopeStore(KeycloakSession session, MapStorage<UUID, MapScopeEntity, Scope> scopeStore, AuthorizationProvider provider) {
public MapScopeStore(KeycloakSession session, MapStorage<K, MapScopeEntity<K>, Scope> scopeStore, AuthorizationProvider provider) {
this.authorizationProvider = provider;
this.scopeStore = scopeStore;
this.tx = scopeStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private MapScopeEntity registerEntityForChanges(MapScopeEntity origEntity) {
final MapScopeEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapScopeEntity::isUpdated);
private MapScopeEntity<K> registerEntityForChanges(MapScopeEntity<K> origEntity) {
final MapScopeEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapScopeEntity<K>::isUpdated);
return res;
}
private Scope entityToAdapter(MapScopeEntity origEntity) {
private Scope entityToAdapter(MapScopeEntity<K> origEntity) {
if (origEntity == null) return null;
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return new MapScopeAdapter(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory());
return new MapScopeAdapter<K>(registerEntityForChanges(origEntity), authorizationProvider.getStoreFactory()) {
@Override
public String getId() {
return scopeStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private ModelCriteriaBuilder<Scope> forResourceServer(String resourceServerId) {
@ -90,8 +94,8 @@ public class MapScopeStore implements ScopeStore {
throw new ModelDuplicateException("Scope with name '" + name + "' for " + resourceServer.getId() + " already exists");
}
UUID uid = id == null ? UUID.randomUUID() : UUID.fromString(id);
MapScopeEntity entity = new MapScopeEntity(uid);
K uid = id == null ? scopeStore.getKeyConvertor().yieldNewUniqueKey(): scopeStore.getKeyConvertor().fromString(id);
MapScopeEntity<K> entity = new MapScopeEntity<>(uid);
entity.setName(name);
entity.setResourceServerId(resourceServer.getId());
@ -104,7 +108,7 @@ public class MapScopeStore implements ScopeStore {
@Override
public void delete(String id) {
LOG.tracef("delete(%s)%s", id, getShortStackTrace());
tx.delete(UUID.fromString(id));
tx.delete(scopeStore.getKeyConvertor().fromString(id));
}
@Override

View file

@ -24,7 +24,7 @@ import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects;
public abstract class AbstractResourceModel<E extends AbstractEntity> extends AbstractAuthorizationModel implements Resource {
public abstract class AbstractResourceModel<E extends AbstractEntity<?>> extends AbstractAuthorizationModel implements Resource {
protected final E entity;

View file

@ -23,23 +23,17 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import java.util.UUID;
import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy;
public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel<MapPermissionTicketEntity> {
public abstract class MapPermissionTicketAdapter<K extends Comparable<K>> extends AbstractPermissionTicketModel<MapPermissionTicketEntity<K>> {
public MapPermissionTicketAdapter(MapPermissionTicketEntity entity, StoreFactory storeFactory) {
public MapPermissionTicketAdapter(MapPermissionTicketEntity<K> entity, StoreFactory storeFactory) {
super(entity, storeFactory);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getOwner() {
return entity.getOwner();
@ -52,13 +46,13 @@ public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel<Ma
@Override
public Resource getResource() {
return storeFactory.getResourceStore().findById(entity.getResourceId().toString(), entity.getResourceServerId());
return storeFactory.getResourceStore().findById(entity.getResourceId(), entity.getResourceServerId());
}
@Override
public Scope getScope() {
if (entity.getScopeId() == null) return null;
return storeFactory.getScopeStore().findById(entity.getScopeId().toString(), entity.getResourceServerId());
return storeFactory.getScopeStore().findById(entity.getScopeId(), entity.getResourceServerId());
}
@Override
@ -90,13 +84,13 @@ public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel<Ma
@Override
public Policy getPolicy() {
if (entity.getPolicyId() == null) return null;
return storeFactory.getPolicyStore().findById(entity.getPolicyId().toString(), entity.getResourceServerId());
return storeFactory.getPolicyStore().findById(entity.getPolicyId(), entity.getResourceServerId());
}
@Override
public void setPolicy(Policy policy) {
if (policy != null) {
entity.setPolicyId(UUID.fromString(policy.getId()));
entity.setPolicyId(policy.getId());
}
}

View file

@ -28,20 +28,14 @@ import org.keycloak.representations.idm.authorization.Logic;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public abstract class MapPolicyAdapter<K> extends AbstractPolicyModel<MapPolicyEntity<K>> {
public MapPolicyAdapter(MapPolicyEntity entity, StoreFactory storeFactory) {
public MapPolicyAdapter(MapPolicyEntity<K> entity, StoreFactory storeFactory) {
super(entity, storeFactory);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getType() {
return entity.getType();
@ -123,7 +117,7 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public Set<Policy> getAssociatedPolicies() {
String resourceServerId = entity.getResourceServerId();
return entity.getAssociatedPoliciesIds().stream()
.map(policyId -> storeFactory.getPolicyStore().findById(policyId.toString(), resourceServerId))
.map(policyId -> storeFactory.getPolicyStore().findById(policyId, resourceServerId))
.collect(Collectors.toSet());
}
@ -131,7 +125,7 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public Set<Resource> getResources() {
String resourceServerId = entity.getResourceServerId();
return entity.getResourceIds().stream()
.map(resourceId -> storeFactory.getResourceStore().findById(resourceId.toString(), resourceServerId))
.map(resourceId -> storeFactory.getResourceStore().findById(resourceId, resourceServerId))
.collect(Collectors.toSet());
}
@ -139,7 +133,7 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public Set<Scope> getScopes() {
String resourceServerId = entity.getResourceServerId();
return entity.getScopeIds().stream()
.map(scopeId -> storeFactory.getScopeStore().findById(scopeId.toString(), resourceServerId))
.map(scopeId -> storeFactory.getScopeStore().findById(scopeId, resourceServerId))
.collect(Collectors.toSet());
}
@ -157,37 +151,37 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
@Override
public void addScope(Scope scope) {
throwExceptionIfReadonly();
entity.addScope(UUID.fromString(scope.getId()));
entity.addScope(scope.getId());
}
@Override
public void removeScope(Scope scope) {
throwExceptionIfReadonly();
entity.removeScope(UUID.fromString(scope.getId()));
entity.removeScope(scope.getId());
}
@Override
public void addAssociatedPolicy(Policy associatedPolicy) {
throwExceptionIfReadonly();
entity.addAssociatedPolicy(UUID.fromString(associatedPolicy.getId()));
entity.addAssociatedPolicy(associatedPolicy.getId());
}
@Override
public void removeAssociatedPolicy(Policy associatedPolicy) {
throwExceptionIfReadonly();
entity.removeAssociatedPolicy(UUID.fromString(associatedPolicy.getId()));
entity.removeAssociatedPolicy(associatedPolicy.getId());
}
@Override
public void addResource(Resource resource) {
throwExceptionIfReadonly();
entity.addResource(UUID.fromString(resource.getId()));
entity.addResource(resource.getId());
}
@Override
public void removeResource(Resource resource) {
throwExceptionIfReadonly();
entity.removeResource(UUID.fromString(resource.getId()));
entity.removeResource(resource.getId());
}
@Override

View file

@ -19,27 +19,21 @@ package org.keycloak.models.map.authorization.adapter;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class MapResourceAdapter extends AbstractResourceModel<MapResourceEntity> {
public abstract class MapResourceAdapter<K> extends AbstractResourceModel<MapResourceEntity<K>> {
public MapResourceAdapter(MapResourceEntity entity, StoreFactory storeFactory) {
public MapResourceAdapter(MapResourceEntity<K> entity, StoreFactory storeFactory) {
super(entity, storeFactory);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getName() {
return entity.getName();
@ -88,7 +82,7 @@ public class MapResourceAdapter extends AbstractResourceModel<MapResourceEntity>
public List<Scope> getScopes() {
return entity.getScopeIds().stream()
.map(id -> storeFactory
.getScopeStore().findById(id.toString(), entity.getResourceServerId()))
.getScopeStore().findById(id, entity.getResourceServerId()))
.collect(Collectors.toList());
}
@ -127,7 +121,7 @@ public class MapResourceAdapter extends AbstractResourceModel<MapResourceEntity>
@Override
public void updateScopes(Set<Scope> scopes) {
throwExceptionIfReadonly();
entity.setScopeIds(scopes.stream().map(Scope::getId).map(UUID::fromString).collect(Collectors.toSet()));
entity.setScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toSet()));
}
@Override

View file

@ -23,17 +23,12 @@ import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
public class MapResourceServerAdapter extends AbstractResourceServerModel<MapResourceServerEntity> {
public abstract class MapResourceServerAdapter<K> extends AbstractResourceServerModel<MapResourceServerEntity<K>> {
public MapResourceServerAdapter(MapResourceServerEntity entity, StoreFactory storeFactory) {
public MapResourceServerAdapter(MapResourceServerEntity<K> entity, StoreFactory storeFactory) {
super(entity, storeFactory);
}
@Override
public String getId() {
return entity.getId();
}
@Override
public boolean isAllowRemoteResourceManagement() {
return entity.isAllowRemoteResourceManagement();

View file

@ -22,17 +22,12 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
public class MapScopeAdapter extends AbstractScopeModel<MapScopeEntity> {
public abstract class MapScopeAdapter<K> extends AbstractScopeModel<MapScopeEntity<K>> {
public MapScopeAdapter(MapScopeEntity entity, StoreFactory storeFactory) {
public MapScopeAdapter(MapScopeEntity<K> entity, StoreFactory storeFactory) {
super(entity, storeFactory);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getName() {
return entity.getName();

View file

@ -1,127 +0,0 @@
/*
* Copyright 2021 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.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects;
public abstract class AbstractPermissionTicketEntity<K> implements AbstractEntity<K> {
private final K id;
private String owner;
private String requester;
private Long createdTimestamp;
private Long grantedTimestamp;
private K resourceId;
private K scopeId;
private String resourceServerId;
private K policyId;
private boolean updated = false;
protected AbstractPermissionTicketEntity(K id) {
this.id = id;
}
public AbstractPermissionTicketEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.updated |= !Objects.equals(this.owner, owner);
this.owner = owner;
}
public String getRequester() {
return requester;
}
public void setRequester(String requester) {
this.updated |= !Objects.equals(this.requester, requester);
this.requester = requester;
}
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long createdTimestamp) {
this.updated |= !Objects.equals(this.createdTimestamp, createdTimestamp);
this.createdTimestamp = createdTimestamp;
}
public Long getGrantedTimestamp() {
return grantedTimestamp;
}
public void setGrantedTimestamp(Long grantedTimestamp) {
this.updated |= !Objects.equals(this.grantedTimestamp, grantedTimestamp);
this.grantedTimestamp = grantedTimestamp;
}
public K getResourceId() {
return resourceId;
}
public void setResourceId(K resourceId) {
this.updated |= !Objects.equals(this.resourceId, resourceId);
this.resourceId = resourceId;
}
public K getScopeId() {
return scopeId;
}
public void setScopeId(K scopeId) {
this.updated |= !Objects.equals(this.scopeId, scopeId);
this.scopeId = scopeId;
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
public K getPolicyId() {
return policyId;
}
public void setPolicyId(K policyId) {
this.updated |= !Objects.equals(this.policyId, policyId);
this.policyId = policyId;
}
@Override
public boolean isUpdated() {
return updated;
}
}

View file

@ -1,191 +0,0 @@
/*
* Copyright 2021 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.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import java.util.Objects;
public abstract class AbstractPolicyEntity<K> implements AbstractEntity<K> {
public static final Comparator<AbstractPolicyEntity<?>> COMPARE_BY_NAME = Comparator.comparing(AbstractPolicyEntity::getName);
private final K id;
private String name;
private String description;
private String type;
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
private Logic logic = Logic.POSITIVE;
private final Map<String, String> config = new HashMap<>();
private String resourceServerId;
private final Set<K> associatedPoliciesIds = new HashSet<>();
private final Set<K> resourceIds = new HashSet<>();
private final Set<K> scopeIds = new HashSet<>();
private String owner;
private boolean updated = false;
protected AbstractPolicyEntity(K id) {
this.id = id;
}
public AbstractPolicyEntity() {
this.id = null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= !Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= !Objects.equals(this.description, description);
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.updated |= !Objects.equals(this.type, type);
this.type = type;
}
public DecisionStrategy getDecisionStrategy() {
return decisionStrategy;
}
public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
this.updated |= !Objects.equals(this.decisionStrategy, decisionStrategy);
this.decisionStrategy = decisionStrategy;
}
public Logic getLogic() {
return logic;
}
public void setLogic(Logic logic) {
this.updated |= !Objects.equals(this.logic, logic);
this.logic = logic;
}
public Map<String, String> getConfig() {
return config;
}
public String getConfigValue(String name) {
return config.get(name);
}
public void setConfig(Map<String, String> config) {
if (Objects.equals(this.config, config)) return;
this.updated = true;
this.config.clear();
if (config != null) {
this.config.putAll(config);
}
}
public void removeConfig(String name) {
this.updated |= this.config.remove(name) != null;
}
public void putConfig(String name, String value) {
this.updated |= !Objects.equals(value, this.config.put(name, value));
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
public Set<K> getAssociatedPoliciesIds() {
return associatedPoliciesIds;
}
public void addAssociatedPolicy(K policyId) {
this.updated |= this.associatedPoliciesIds.add(policyId);
}
public void removeAssociatedPolicy(K policyId) {
this.updated |= this.associatedPoliciesIds.remove(policyId);
}
public Set<K> getResourceIds() {
return resourceIds;
}
public void addResource(K resourceId) {
this.updated |= this.resourceIds.add(resourceId);
}
public void removeResource(K resourceId) {
this.updated |= this.resourceIds.remove(resourceId);
}
public Set<K> getScopeIds() {
return scopeIds;
}
public void addScope(K scopeId) {
this.updated |= this.scopeIds.add(scopeId);
}
public void removeScope(K scopeId) {
this.updated |= this.scopeIds.remove(scopeId);
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.updated |= !Objects.equals(this.owner, owner);
this.owner = owner;
}
@Override
public K getId() {
return id;
}
@Override
public boolean isUpdated() {
return updated;
}
}

View file

@ -1,183 +0,0 @@
/*
* Copyright 2021 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.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public abstract class AbstractResourceEntity<K> implements AbstractEntity<K> {
public static final Comparator<AbstractResourceEntity<?>> COMPARE_BY_NAME = Comparator.comparing(AbstractResourceEntity::getName);
private final K id;
private String name;
private String displayName;
private final Set<String> uris = new HashSet<>();
private String type;
private String iconUri;
private String owner;
private boolean ownerManagedAccess;
private String resourceServerId;
private final Set<K> scopeIds = new HashSet<>();
private final Set<K> policyIds = new HashSet<>();
private final Map<String, List<String>> attributes = new HashMap<>();
private boolean updated = false;
protected AbstractResourceEntity(K id) {
this.id = id;
}
public AbstractResourceEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= !Objects.equals(this.name, name);
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.updated |= !Objects.equals(this.displayName, displayName);
this.displayName = displayName;
}
public Set<String> getUris() {
return uris;
}
public void setUris(Set<String> uris) {
if (Objects.equals(this.uris, uris)) return;
this.updated = true;
this.uris.clear();
if (uris != null) {
this.uris.addAll(uris);
}
}
public String getType() {
return type;
}
public void setType(String type) {
this.updated |= !Objects.equals(this.type, type);
this.type = type;
}
public String getIconUri() {
return iconUri;
}
public void setIconUri(String iconUri) {
this.updated |= !Objects.equals(this.iconUri, iconUri);
this.iconUri = iconUri;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.updated |= !Objects.equals(this.owner, owner);
this.owner = owner;
}
public boolean isOwnerManagedAccess() {
return ownerManagedAccess;
}
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
this.updated |= this.ownerManagedAccess != ownerManagedAccess;
this.ownerManagedAccess = ownerManagedAccess;
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
public Set<K> getScopeIds() {
return scopeIds;
}
public void setScopeIds(Set<K> scopeIds) {
if (Objects.equals(this.scopeIds, scopeIds)) return;
this.updated = true;
this.scopeIds.clear();
if (scopeIds != null) {
this.scopeIds.addAll(scopeIds);
}
}
public Set<K> getPolicyIds() {
return policyIds;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public List<String> getAttribute(String name) {
return attributes.get(name);
}
public String getSingleAttribute(String name) {
List<String> attributeValues = attributes.get(name);
return attributeValues == null || attributeValues.isEmpty() ? null : attributeValues.get(0);
}
public void setAttribute(String name, List<String> value) {
this.updated |= !Objects.equals(this.attributes.put(name, value), value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
@Override
public boolean isUpdated() {
return updated;
}
}

View file

@ -1,79 +0,0 @@
/*
* Copyright 2021 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.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
import java.util.Objects;
public abstract class AbstractResourceServerEntity<K> implements AbstractEntity<K> {
private final K id;
private boolean updated = false;
private boolean allowRemoteResourceManagement;
private PolicyEnforcementMode policyEnforcementMode = PolicyEnforcementMode.ENFORCING;
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
protected AbstractResourceServerEntity(K id) {
this.id = id;
}
public AbstractResourceServerEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public boolean isAllowRemoteResourceManagement() {
return allowRemoteResourceManagement;
}
public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) {
this.updated |= this.allowRemoteResourceManagement != allowRemoteResourceManagement;
this.allowRemoteResourceManagement = allowRemoteResourceManagement;
}
public PolicyEnforcementMode getPolicyEnforcementMode() {
return policyEnforcementMode;
}
public void setPolicyEnforcementMode(PolicyEnforcementMode policyEnforcementMode) {
this.updated |= !Objects.equals(this.policyEnforcementMode, policyEnforcementMode);
this.policyEnforcementMode = policyEnforcementMode;
}
public DecisionStrategy getDecisionStrategy() {
return decisionStrategy;
}
public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
this.updated |= !Objects.equals(this.decisionStrategy, decisionStrategy);
this.decisionStrategy = decisionStrategy;
}
@Override
public boolean isUpdated() {
return updated;
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright 2021 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.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects;
public abstract class AbstractScopeEntity<K> implements AbstractEntity<K> {
private final K id;
private String name;
private String displayName;
private String iconUri;
private String resourceServerId;
private boolean updated = false;
protected AbstractScopeEntity(K id) {
this.id = id;
}
public AbstractScopeEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= !Objects.equals(this.name, name);
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.updated |= !Objects.equals(this.displayName, displayName);
this.displayName = displayName;
}
public String getIconUri() {
return iconUri;
}
public void setIconUri(String iconUri) {
this.updated |= !Objects.equals(this.iconUri, iconUri);
this.iconUri = iconUri;
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
@Override
public boolean isUpdated() {
return updated;
}
}

View file

@ -17,21 +17,114 @@
package org.keycloak.models.map.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Comparator;
import java.util.UUID;
import java.util.Objects;
public class MapPermissionTicketEntity extends AbstractPermissionTicketEntity<UUID> {
public class MapPermissionTicketEntity<K> implements AbstractEntity<K> {
public static final Comparator<AbstractPermissionTicketEntity<UUID>> COMPARE_BY_ID = Comparator.comparing(AbstractPermissionTicketEntity::getId);
public static final Comparator<AbstractPermissionTicketEntity<UUID>> COMPARE_BY_RESOURCE_ID = Comparator.comparing(AbstractPermissionTicketEntity::getResourceId);
public static final Comparator<MapPermissionTicketEntity<?>> COMPARE_BY_RESOURCE_ID = Comparator.comparing(MapPermissionTicketEntity::getResourceId);
private final K id;
private String owner;
private String requester;
private Long createdTimestamp;
private Long grantedTimestamp;
private String resourceId;
private String scopeId;
private String resourceServerId;
private String policyId;
private boolean updated = false;
protected MapPermissionTicketEntity() {
super();
public MapPermissionTicketEntity(K id) {
this.id = id;
}
public MapPermissionTicketEntity(UUID id) {
super(id);
public MapPermissionTicketEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.updated |= !Objects.equals(this.owner, owner);
this.owner = owner;
}
public String getRequester() {
return requester;
}
public void setRequester(String requester) {
this.updated |= !Objects.equals(this.requester, requester);
this.requester = requester;
}
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long createdTimestamp) {
this.updated |= !Objects.equals(this.createdTimestamp, createdTimestamp);
this.createdTimestamp = createdTimestamp;
}
public Long getGrantedTimestamp() {
return grantedTimestamp;
}
public void setGrantedTimestamp(Long grantedTimestamp) {
this.updated |= !Objects.equals(this.grantedTimestamp, grantedTimestamp);
this.grantedTimestamp = grantedTimestamp;
}
public String getResourceId() {
return resourceId;
}
public void setResourceId(String resourceId) {
this.updated |= !Objects.equals(this.resourceId, resourceId);
this.resourceId = resourceId;
}
public String getScopeId() {
return scopeId;
}
public void setScopeId(String scopeId) {
this.updated |= !Objects.equals(this.scopeId, scopeId);
this.scopeId = scopeId;
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
public String getPolicyId() {
return policyId;
}
public void setPolicyId(String policyId) {
this.updated |= !Objects.equals(this.policyId, policyId);
this.policyId = policyId;
}
@Override
public boolean isUpdated() {
return updated;
}
@Override

View file

@ -17,15 +17,176 @@
package org.keycloak.models.map.authorization.entity;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic;
public class MapPolicyEntity extends AbstractPolicyEntity<UUID> {
protected MapPolicyEntity() {
super();
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import java.util.Objects;
public class MapPolicyEntity<K> implements AbstractEntity<K> {
public static final Comparator<MapPolicyEntity<?>> COMPARE_BY_NAME = Comparator.comparing(MapPolicyEntity::getName);
private final K id;
private String name;
private String description;
private String type;
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
private Logic logic = Logic.POSITIVE;
private final Map<String, String> config = new HashMap<>();
private String resourceServerId;
private final Set<String> associatedPoliciesIds = new HashSet<>();
private final Set<String> resourceIds = new HashSet<>();
private final Set<String> scopeIds = new HashSet<>();
private String owner;
private boolean updated = false;
public MapPolicyEntity(K id) {
this.id = id;
}
public MapPolicyEntity(UUID id) {
super(id);
public MapPolicyEntity() {
this.id = null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= !Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= !Objects.equals(this.description, description);
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.updated |= !Objects.equals(this.type, type);
this.type = type;
}
public DecisionStrategy getDecisionStrategy() {
return decisionStrategy;
}
public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
this.updated |= !Objects.equals(this.decisionStrategy, decisionStrategy);
this.decisionStrategy = decisionStrategy;
}
public Logic getLogic() {
return logic;
}
public void setLogic(Logic logic) {
this.updated |= !Objects.equals(this.logic, logic);
this.logic = logic;
}
public Map<String, String> getConfig() {
return config;
}
public String getConfigValue(String name) {
return config.get(name);
}
public void setConfig(Map<String, String> config) {
if (Objects.equals(this.config, config)) return;
this.updated = true;
this.config.clear();
if (config != null) {
this.config.putAll(config);
}
}
public void removeConfig(String name) {
this.updated |= this.config.remove(name) != null;
}
public void putConfig(String name, String value) {
this.updated |= !Objects.equals(value, this.config.put(name, value));
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
public Set<String> getAssociatedPoliciesIds() {
return associatedPoliciesIds;
}
public void addAssociatedPolicy(String policyId) {
this.updated |= this.associatedPoliciesIds.add(policyId);
}
public void removeAssociatedPolicy(String policyId) {
this.updated |= this.associatedPoliciesIds.remove(policyId);
}
public Set<String> getResourceIds() {
return resourceIds;
}
public void addResource(String resourceId) {
this.updated |= this.resourceIds.add(resourceId);
}
public void removeResource(String resourceId) {
this.updated |= this.resourceIds.remove(resourceId);
}
public Set<String> getScopeIds() {
return scopeIds;
}
public void addScope(String scopeId) {
this.updated |= this.scopeIds.add(scopeId);
}
public void removeScope(String scopeId) {
this.updated |= this.scopeIds.remove(scopeId);
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.updated |= !Objects.equals(this.owner, owner);
this.owner = owner;
}
@Override
public K getId() {
return id;
}
@Override
public boolean isUpdated() {
return updated;
}
@Override

View file

@ -17,18 +17,167 @@
package org.keycloak.models.map.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Comparator;
import java.util.UUID;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class MapResourceEntity extends AbstractResourceEntity<UUID> {
public static final Comparator<AbstractResourceEntity<UUID>> COMPARE_BY_ID = Comparator.comparing(AbstractResourceEntity::getId);
public class MapResourceEntity<K> implements AbstractEntity<K> {
protected MapResourceEntity() {
super();
public static final Comparator<MapResourceEntity<?>> COMPARE_BY_NAME = Comparator.comparing(MapResourceEntity::getName);
private final K id;
private String name;
private String displayName;
private final Set<String> uris = new HashSet<>();
private String type;
private String iconUri;
private String owner;
private boolean ownerManagedAccess;
private String resourceServerId;
private final Set<String> scopeIds = new HashSet<>();
private final Set<String> policyIds = new HashSet<>();
private final Map<String, List<String>> attributes = new HashMap<>();
private boolean updated = false;
public MapResourceEntity(K id) {
this.id = id;
}
public MapResourceEntity(UUID id) {
super(id);
public MapResourceEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= !Objects.equals(this.name, name);
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.updated |= !Objects.equals(this.displayName, displayName);
this.displayName = displayName;
}
public Set<String> getUris() {
return uris;
}
public void setUris(Set<String> uris) {
if (Objects.equals(this.uris, uris)) return;
this.updated = true;
this.uris.clear();
if (uris != null) {
this.uris.addAll(uris);
}
}
public String getType() {
return type;
}
public void setType(String type) {
this.updated |= !Objects.equals(this.type, type);
this.type = type;
}
public String getIconUri() {
return iconUri;
}
public void setIconUri(String iconUri) {
this.updated |= !Objects.equals(this.iconUri, iconUri);
this.iconUri = iconUri;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.updated |= !Objects.equals(this.owner, owner);
this.owner = owner;
}
public boolean isOwnerManagedAccess() {
return ownerManagedAccess;
}
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
this.updated |= this.ownerManagedAccess != ownerManagedAccess;
this.ownerManagedAccess = ownerManagedAccess;
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
public Set<String> getScopeIds() {
return scopeIds;
}
public void setScopeIds(Set<String> scopeIds) {
if (Objects.equals(this.scopeIds, scopeIds)) return;
this.updated = true;
this.scopeIds.clear();
if (scopeIds != null) {
this.scopeIds.addAll(scopeIds);
}
}
public Set<String> getPolicyIds() {
return policyIds;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public List<String> getAttribute(String name) {
return attributes.get(name);
}
public String getSingleAttribute(String name) {
List<String> attributeValues = attributes.get(name);
return attributeValues == null || attributeValues.isEmpty() ? null : attributeValues.get(0);
}
public void setAttribute(String name, List<String> value) {
this.updated |= !Objects.equals(this.attributes.put(name, value), value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
@Override
public boolean isUpdated() {
return updated;
}
@Override

View file

@ -17,13 +17,64 @@
package org.keycloak.models.map.authorization.entity;
public class MapResourceServerEntity extends AbstractResourceServerEntity<String> {
protected MapResourceServerEntity() {
super();
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
import java.util.Objects;
public class MapResourceServerEntity<K> implements AbstractEntity<K> {
private final K id;
private boolean updated = false;
private boolean allowRemoteResourceManagement;
private PolicyEnforcementMode policyEnforcementMode = PolicyEnforcementMode.ENFORCING;
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
public MapResourceServerEntity(K id) {
this.id = id;
}
public MapResourceServerEntity(String id) {
super(id);
public MapResourceServerEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public boolean isAllowRemoteResourceManagement() {
return allowRemoteResourceManagement;
}
public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) {
this.updated |= this.allowRemoteResourceManagement != allowRemoteResourceManagement;
this.allowRemoteResourceManagement = allowRemoteResourceManagement;
}
public PolicyEnforcementMode getPolicyEnforcementMode() {
return policyEnforcementMode;
}
public void setPolicyEnforcementMode(PolicyEnforcementMode policyEnforcementMode) {
this.updated |= !Objects.equals(this.policyEnforcementMode, policyEnforcementMode);
this.policyEnforcementMode = policyEnforcementMode;
}
public DecisionStrategy getDecisionStrategy() {
return decisionStrategy;
}
public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
this.updated |= !Objects.equals(this.decisionStrategy, decisionStrategy);
this.decisionStrategy = decisionStrategy;
}
@Override
public boolean isUpdated() {
return updated;
}
@Override

View file

@ -17,15 +17,71 @@
package org.keycloak.models.map.authorization.entity;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractEntity;
public class MapScopeEntity extends AbstractScopeEntity<UUID> {
protected MapScopeEntity() {
super();
import java.util.Objects;
public class MapScopeEntity<K> implements AbstractEntity<K> {
private final K id;
private String name;
private String displayName;
private String iconUri;
private String resourceServerId;
private boolean updated = false;
public MapScopeEntity(K id) {
this.id = id;
}
public MapScopeEntity(UUID id) {
super(id);
public MapScopeEntity() {
this.id = null;
}
@Override
public K getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= !Objects.equals(this.name, name);
this.name = name;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.updated |= !Objects.equals(this.displayName, displayName);
this.displayName = displayName;
}
public String getIconUri() {
return iconUri;
}
public void setIconUri(String iconUri) {
this.updated |= !Objects.equals(this.iconUri, iconUri);
this.iconUri = iconUri;
}
public String getResourceServerId() {
return resourceServerId;
}
public void setResourceServerId(String resourceServerId) {
this.updated |= !Objects.equals(this.resourceServerId, resourceServerId);
this.resourceServerId = resourceServerId;
}
@Override
public boolean isUpdated() {
return updated;
}
@Override

View file

@ -1,482 +0,0 @@
/*
* Copyright 2020 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.client;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author hmlnarik
*/
public abstract class AbstractClientEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String clientId;
private String name;
private String description;
private Set<String> redirectUris = new HashSet<>();
private boolean enabled;
private boolean alwaysDisplayInConsole;
private String clientAuthenticatorType;
private String secret;
private String registrationToken;
private String protocol;
private Map<String, String> attributes = new HashMap<>();
private Map<String, String> authFlowBindings = new HashMap<>();
private boolean publicClient;
private boolean fullScopeAllowed;
private boolean frontchannelLogout;
private int notBefore;
private Set<String> scope = new HashSet<>();
private Set<String> webOrigins = new HashSet<>();
private Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
private Map<String, Boolean> clientScopes = new HashMap<>();
private Set<String> scopeMappings = new LinkedHashSet<>();
private boolean surrogateAuthRequired;
private String managementUrl;
private String rootUrl;
private String baseUrl;
private boolean bearerOnly;
private boolean consentRequired;
private boolean standardFlowEnabled;
private boolean implicitFlowEnabled;
private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private int nodeReRegistrationTimeout;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected AbstractClientEntity() {
this.id = null;
this.realmId = null;
}
public AbstractClientEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= ! Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public Set<String> getRedirectUris() {
return redirectUris;
}
public void setRedirectUris(Set<String> redirectUris) {
this.updated |= ! Objects.equals(this.redirectUris, redirectUris);
this.redirectUris = redirectUris;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.updated |= ! Objects.equals(this.enabled, enabled);
this.enabled = enabled;
}
public boolean isAlwaysDisplayInConsole() {
return alwaysDisplayInConsole;
}
public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) {
this.updated |= ! Objects.equals(this.alwaysDisplayInConsole, alwaysDisplayInConsole);
this.alwaysDisplayInConsole = alwaysDisplayInConsole;
}
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.updated |= ! Objects.equals(this.clientAuthenticatorType, clientAuthenticatorType);
this.clientAuthenticatorType = clientAuthenticatorType;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.updated |= ! Objects.equals(this.secret, secret);
this.secret = secret;
}
public String getRegistrationToken() {
return registrationToken;
}
public void setRegistrationToken(String registrationToken) {
this.updated |= ! Objects.equals(this.registrationToken, registrationToken);
this.registrationToken = registrationToken;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.updated |= ! Objects.equals(this.protocol, protocol);
this.protocol = protocol;
}
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public Map<String, String> getAuthFlowBindings() {
return authFlowBindings;
}
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
this.updated |= ! Objects.equals(this.authFlowBindings, authFlowBindings);
this.authFlowBindings = authFlowBindings;
}
public boolean isPublicClient() {
return publicClient;
}
public void setPublicClient(boolean publicClient) {
this.updated |= ! Objects.equals(this.publicClient, publicClient);
this.publicClient = publicClient;
}
public boolean isFullScopeAllowed() {
return fullScopeAllowed;
}
public void setFullScopeAllowed(boolean fullScopeAllowed) {
this.updated |= ! Objects.equals(this.fullScopeAllowed, fullScopeAllowed);
this.fullScopeAllowed = fullScopeAllowed;
}
public boolean isFrontchannelLogout() {
return frontchannelLogout;
}
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.updated |= ! Objects.equals(this.frontchannelLogout, frontchannelLogout);
this.frontchannelLogout = frontchannelLogout;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.updated |= ! Objects.equals(this.notBefore, notBefore);
this.notBefore = notBefore;
}
public Set<String> getScope() {
return scope;
}
public void setScope(Set<String> scope) {
this.updated |= ! Objects.equals(this.scope, scope);
this.scope.clear();
this.scope.addAll(scope);
}
public Set<String> getWebOrigins() {
return webOrigins;
}
public void setWebOrigins(Set<String> webOrigins) {
this.updated |= ! Objects.equals(this.webOrigins, webOrigins);
this.webOrigins.clear();
this.webOrigins.addAll(webOrigins);
}
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
Objects.requireNonNull(model.getId(), "protocolMapper.id");
updated = true;
this.protocolMappers.put(model.getId(), model);
return model;
}
public Collection<ProtocolMapperModel> getProtocolMappers() {
return protocolMappers.values();
}
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
updated = true;
protocolMappers.put(id, mapping);
}
public void removeProtocolMapper(String id) {
updated |= protocolMappers.remove(id) != null;
}
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
this.protocolMappers.clear();
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
}
public ProtocolMapperModel getProtocolMapperById(String id) {
return id == null ? null : protocolMappers.get(id);
}
public boolean isSurrogateAuthRequired() {
return surrogateAuthRequired;
}
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
this.updated |= ! Objects.equals(this.surrogateAuthRequired, surrogateAuthRequired);
this.surrogateAuthRequired = surrogateAuthRequired;
}
public String getManagementUrl() {
return managementUrl;
}
public void setManagementUrl(String managementUrl) {
this.updated |= ! Objects.equals(this.managementUrl, managementUrl);
this.managementUrl = managementUrl;
}
public String getRootUrl() {
return rootUrl;
}
public void setRootUrl(String rootUrl) {
this.updated |= ! Objects.equals(this.rootUrl, rootUrl);
this.rootUrl = rootUrl;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.updated |= ! Objects.equals(this.baseUrl, baseUrl);
this.baseUrl = baseUrl;
}
public boolean isBearerOnly() {
return bearerOnly;
}
public void setBearerOnly(boolean bearerOnly) {
this.updated |= ! Objects.equals(this.bearerOnly, bearerOnly);
this.bearerOnly = bearerOnly;
}
public boolean isConsentRequired() {
return consentRequired;
}
public void setConsentRequired(boolean consentRequired) {
this.updated |= ! Objects.equals(this.consentRequired, consentRequired);
this.consentRequired = consentRequired;
}
public boolean isStandardFlowEnabled() {
return standardFlowEnabled;
}
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
this.updated |= ! Objects.equals(this.standardFlowEnabled, standardFlowEnabled);
this.standardFlowEnabled = standardFlowEnabled;
}
public boolean isImplicitFlowEnabled() {
return implicitFlowEnabled;
}
public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
this.updated |= ! Objects.equals(this.implicitFlowEnabled, implicitFlowEnabled);
this.implicitFlowEnabled = implicitFlowEnabled;
}
public boolean isDirectAccessGrantsEnabled() {
return directAccessGrantsEnabled;
}
public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
this.updated |= ! Objects.equals(this.directAccessGrantsEnabled, directAccessGrantsEnabled);
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
}
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
this.updated |= ! Objects.equals(this.serviceAccountsEnabled, serviceAccountsEnabled);
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
public int getNodeReRegistrationTimeout() {
return nodeReRegistrationTimeout;
}
public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
this.updated |= ! Objects.equals(this.nodeReRegistrationTimeout, nodeReRegistrationTimeout);
this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
}
public void addWebOrigin(String webOrigin) {
updated = true;
this.webOrigins.add(webOrigin);
}
public void removeWebOrigin(String webOrigin) {
updated |= this.webOrigins.remove(webOrigin);
}
public void addRedirectUri(String redirectUri) {
this.updated |= ! this.redirectUris.contains(redirectUri);
this.redirectUris.add(redirectUri);
}
public void removeRedirectUri(String redirectUri) {
updated |= this.redirectUris.remove(redirectUri);
}
public void setAttribute(String name, String value) {
this.updated = true;
this.attributes.put(name, value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getAttribute(String name) {
return this.attributes.get(name);
}
public String getAuthenticationFlowBindingOverride(String binding) {
return this.authFlowBindings.get(binding);
}
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return this.authFlowBindings;
}
public void removeAuthenticationFlowBindingOverride(String binding) {
updated |= this.authFlowBindings.remove(binding) != null;
}
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
this.updated = true;
this.authFlowBindings.put(binding, flowId);
}
public Collection<String> getScopeMappings() {
return scopeMappings;
}
public void addScopeMapping(String id) {
if (id != null) {
updated = true;
scopeMappings.add(id);
}
}
public void deleteScopeMapping(String id) {
updated |= scopeMappings.remove(id);
}
public void addClientScope(String id, boolean defaultScope) {
if (id != null) {
updated = true;
this.clientScopes.put(id, defaultScope);
}
}
public void removeClientScope(String id) {
if (id != null) {
updated |= clientScopes.remove(id) != null;
}
}
public Stream<String> getClientScopes(boolean defaultScope) {
return this.clientScopes.entrySet().stream()
.filter(me -> Objects.equals(me.getValue(), defaultScope))
.map(Entry::getKey);
}
public String getRealmId() {
return this.realmId;
}
}

View file

@ -33,17 +33,12 @@ import java.util.stream.Stream;
*
* @author hmlnarik
*/
public abstract class MapClientAdapter extends AbstractClientModel<MapClientEntity> implements ClientModel {
public abstract class MapClientAdapter<K> extends AbstractClientModel<MapClientEntity<K>> implements ClientModel {
public MapClientAdapter(KeycloakSession session, RealmModel realm, MapClientEntity entity) {
public MapClientAdapter(KeycloakSession session, RealmModel realm, MapClientEntity<K> entity) {
super(session, realm, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getClientId() {
return entity.getClientId();

View file

@ -16,20 +16,467 @@
*/
package org.keycloak.models.map.client;
import java.util.UUID;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author hmlnarik
*/
public class MapClientEntity extends AbstractClientEntity<UUID> {
public class MapClientEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String clientId;
private String name;
private String description;
private Set<String> redirectUris = new HashSet<>();
private boolean enabled;
private boolean alwaysDisplayInConsole;
private String clientAuthenticatorType;
private String secret;
private String registrationToken;
private String protocol;
private Map<String, String> attributes = new HashMap<>();
private Map<String, String> authFlowBindings = new HashMap<>();
private boolean publicClient;
private boolean fullScopeAllowed;
private boolean frontchannelLogout;
private int notBefore;
private Set<String> scope = new HashSet<>();
private Set<String> webOrigins = new HashSet<>();
private Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
private Map<String, Boolean> clientScopes = new HashMap<>();
private Set<String> scopeMappings = new LinkedHashSet<>();
private boolean surrogateAuthRequired;
private String managementUrl;
private String rootUrl;
private String baseUrl;
private boolean bearerOnly;
private boolean consentRequired;
private boolean standardFlowEnabled;
private boolean implicitFlowEnabled;
private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private int nodeReRegistrationTimeout;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected MapClientEntity() {
super();
this.id = null;
this.realmId = null;
}
public MapClientEntity(UUID id, String realmId) {
super(id, realmId);
public MapClientEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= ! Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public Set<String> getRedirectUris() {
return redirectUris;
}
public void setRedirectUris(Set<String> redirectUris) {
this.updated |= ! Objects.equals(this.redirectUris, redirectUris);
this.redirectUris = redirectUris;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.updated |= ! Objects.equals(this.enabled, enabled);
this.enabled = enabled;
}
public boolean isAlwaysDisplayInConsole() {
return alwaysDisplayInConsole;
}
public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) {
this.updated |= ! Objects.equals(this.alwaysDisplayInConsole, alwaysDisplayInConsole);
this.alwaysDisplayInConsole = alwaysDisplayInConsole;
}
public String getClientAuthenticatorType() {
return clientAuthenticatorType;
}
public void setClientAuthenticatorType(String clientAuthenticatorType) {
this.updated |= ! Objects.equals(this.clientAuthenticatorType, clientAuthenticatorType);
this.clientAuthenticatorType = clientAuthenticatorType;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.updated |= ! Objects.equals(this.secret, secret);
this.secret = secret;
}
public String getRegistrationToken() {
return registrationToken;
}
public void setRegistrationToken(String registrationToken) {
this.updated |= ! Objects.equals(this.registrationToken, registrationToken);
this.registrationToken = registrationToken;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.updated |= ! Objects.equals(this.protocol, protocol);
this.protocol = protocol;
}
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public Map<String, String> getAuthFlowBindings() {
return authFlowBindings;
}
public void setAuthFlowBindings(Map<String, String> authFlowBindings) {
this.updated |= ! Objects.equals(this.authFlowBindings, authFlowBindings);
this.authFlowBindings = authFlowBindings;
}
public boolean isPublicClient() {
return publicClient;
}
public void setPublicClient(boolean publicClient) {
this.updated |= ! Objects.equals(this.publicClient, publicClient);
this.publicClient = publicClient;
}
public boolean isFullScopeAllowed() {
return fullScopeAllowed;
}
public void setFullScopeAllowed(boolean fullScopeAllowed) {
this.updated |= ! Objects.equals(this.fullScopeAllowed, fullScopeAllowed);
this.fullScopeAllowed = fullScopeAllowed;
}
public boolean isFrontchannelLogout() {
return frontchannelLogout;
}
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.updated |= ! Objects.equals(this.frontchannelLogout, frontchannelLogout);
this.frontchannelLogout = frontchannelLogout;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.updated |= ! Objects.equals(this.notBefore, notBefore);
this.notBefore = notBefore;
}
public Set<String> getScope() {
return scope;
}
public void setScope(Set<String> scope) {
this.updated |= ! Objects.equals(this.scope, scope);
this.scope.clear();
this.scope.addAll(scope);
}
public Set<String> getWebOrigins() {
return webOrigins;
}
public void setWebOrigins(Set<String> webOrigins) {
this.updated |= ! Objects.equals(this.webOrigins, webOrigins);
this.webOrigins.clear();
this.webOrigins.addAll(webOrigins);
}
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
Objects.requireNonNull(model.getId(), "protocolMapper.id");
updated = true;
this.protocolMappers.put(model.getId(), model);
return model;
}
public Collection<ProtocolMapperModel> getProtocolMappers() {
return protocolMappers.values();
}
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
updated = true;
protocolMappers.put(id, mapping);
}
public void removeProtocolMapper(String id) {
updated |= protocolMappers.remove(id) != null;
}
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
this.protocolMappers.clear();
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
}
public ProtocolMapperModel getProtocolMapperById(String id) {
return id == null ? null : protocolMappers.get(id);
}
public boolean isSurrogateAuthRequired() {
return surrogateAuthRequired;
}
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
this.updated |= ! Objects.equals(this.surrogateAuthRequired, surrogateAuthRequired);
this.surrogateAuthRequired = surrogateAuthRequired;
}
public String getManagementUrl() {
return managementUrl;
}
public void setManagementUrl(String managementUrl) {
this.updated |= ! Objects.equals(this.managementUrl, managementUrl);
this.managementUrl = managementUrl;
}
public String getRootUrl() {
return rootUrl;
}
public void setRootUrl(String rootUrl) {
this.updated |= ! Objects.equals(this.rootUrl, rootUrl);
this.rootUrl = rootUrl;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.updated |= ! Objects.equals(this.baseUrl, baseUrl);
this.baseUrl = baseUrl;
}
public boolean isBearerOnly() {
return bearerOnly;
}
public void setBearerOnly(boolean bearerOnly) {
this.updated |= ! Objects.equals(this.bearerOnly, bearerOnly);
this.bearerOnly = bearerOnly;
}
public boolean isConsentRequired() {
return consentRequired;
}
public void setConsentRequired(boolean consentRequired) {
this.updated |= ! Objects.equals(this.consentRequired, consentRequired);
this.consentRequired = consentRequired;
}
public boolean isStandardFlowEnabled() {
return standardFlowEnabled;
}
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
this.updated |= ! Objects.equals(this.standardFlowEnabled, standardFlowEnabled);
this.standardFlowEnabled = standardFlowEnabled;
}
public boolean isImplicitFlowEnabled() {
return implicitFlowEnabled;
}
public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
this.updated |= ! Objects.equals(this.implicitFlowEnabled, implicitFlowEnabled);
this.implicitFlowEnabled = implicitFlowEnabled;
}
public boolean isDirectAccessGrantsEnabled() {
return directAccessGrantsEnabled;
}
public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
this.updated |= ! Objects.equals(this.directAccessGrantsEnabled, directAccessGrantsEnabled);
this.directAccessGrantsEnabled = directAccessGrantsEnabled;
}
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
this.updated |= ! Objects.equals(this.serviceAccountsEnabled, serviceAccountsEnabled);
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
public int getNodeReRegistrationTimeout() {
return nodeReRegistrationTimeout;
}
public void setNodeReRegistrationTimeout(int nodeReRegistrationTimeout) {
this.updated |= ! Objects.equals(this.nodeReRegistrationTimeout, nodeReRegistrationTimeout);
this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
}
public void addWebOrigin(String webOrigin) {
updated = true;
this.webOrigins.add(webOrigin);
}
public void removeWebOrigin(String webOrigin) {
updated |= this.webOrigins.remove(webOrigin);
}
public void addRedirectUri(String redirectUri) {
this.updated |= ! this.redirectUris.contains(redirectUri);
this.redirectUris.add(redirectUri);
}
public void removeRedirectUri(String redirectUri) {
updated |= this.redirectUris.remove(redirectUri);
}
public void setAttribute(String name, String value) {
this.updated = true;
this.attributes.put(name, value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getAttribute(String name) {
return this.attributes.get(name);
}
public String getAuthenticationFlowBindingOverride(String binding) {
return this.authFlowBindings.get(binding);
}
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return this.authFlowBindings;
}
public void removeAuthenticationFlowBindingOverride(String binding) {
updated |= this.authFlowBindings.remove(binding) != null;
}
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
this.updated = true;
this.authFlowBindings.put(binding, flowId);
}
public Collection<String> getScopeMappings() {
return scopeMappings;
}
public void addScopeMapping(String id) {
if (id != null) {
updated = true;
scopeMappings.add(id);
}
}
public void deleteScopeMapping(String id) {
updated |= scopeMappings.remove(id);
}
public void addClientScope(String id, boolean defaultScope) {
if (id != null) {
updated = true;
this.clientScopes.put(id, defaultScope);
}
}
public void removeClientScope(String id) {
if (id != null) {
updated |= clientScopes.remove(id) != null;
}
}
public Stream<String> getClientScopes(boolean defaultScope) {
return this.clientScopes.entrySet().stream()
.filter(me -> Objects.equals(me.getValue(), defaultScope))
.map(Entry::getKey);
}
public String getRealmId() {
return this.realmId;
}
}

View file

@ -30,11 +30,9 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.common.Serialization;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
@ -50,18 +48,17 @@ import org.keycloak.models.ClientScopeModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapClientProvider implements ClientProvider {
public class MapClientProvider<K> implements ClientProvider {
private static final Logger LOG = Logger.getLogger(MapClientProvider.class);
private static final Predicate<MapClientEntity> ALWAYS_FALSE = c -> { return false; };
private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapClientEntity, ClientModel> tx;
private final MapStorage<UUID, MapClientEntity, ClientModel> clientStore;
private final ConcurrentMap<UUID, ConcurrentMap<String, Integer>> clientRegisteredNodesStore;
final MapKeycloakTransaction<K, MapClientEntity<K>, ClientModel> tx;
private final MapStorage<K, MapClientEntity<K>, ClientModel> clientStore;
private final ConcurrentMap<K, ConcurrentMap<String, Integer>> clientRegisteredNodesStore;
private static final Comparator<MapClientEntity> COMPARE_BY_CLIENT_ID = Comparator.comparing(MapClientEntity::getClientId);
public MapClientProvider(KeycloakSession session, MapStorage<UUID, MapClientEntity, ClientModel> clientStore, ConcurrentMap<UUID, ConcurrentMap<String, Integer>> clientRegisteredNodesStore) {
public MapClientProvider(KeycloakSession session, MapStorage<K, MapClientEntity<K>, ClientModel> clientStore, ConcurrentMap<K, ConcurrentMap<String, Integer>> clientRegisteredNodesStore) {
this.session = session;
this.clientStore = clientStore;
this.clientRegisteredNodesStore = clientRegisteredNodesStore;
@ -83,16 +80,21 @@ public class MapClientProvider implements ClientProvider {
};
}
private MapClientEntity registerEntityForChanges(MapClientEntity origEntity) {
final MapClientEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapClientEntity::isUpdated);
private MapClientEntity<K> registerEntityForChanges(MapClientEntity<K> origEntity) {
final MapClientEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapClientEntity<K>::isUpdated);
return res;
}
private Function<MapClientEntity, ClientModel> entityToAdapterFunc(RealmModel realm) {
private Function<MapClientEntity<K>, ClientModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapClientAdapter(session, realm, registerEntityForChanges(origEntity)) {
return origEntity -> new MapClientAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return clientStore.getKeyConvertor().keyToString(entity.getId());
}
@Override
public void updateClient() {
LOG.tracef("updateClient(%s)%s", realm, origEntity.getId(), getShortStackTrace());
@ -119,9 +121,9 @@ public class MapClientProvider implements ClientProvider {
};
}
private Predicate<MapClientEntity> entityRealmFilter(RealmModel realm) {
private Predicate<MapClientEntity<K>> entityRealmFilter(RealmModel realm) {
if (realm == null || realm.getId() == null) {
return MapClientProvider.ALWAYS_FALSE;
return c -> false;
}
String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId());
@ -145,7 +147,7 @@ public class MapClientProvider implements ClientProvider {
@Override
public ClientModel addClient(RealmModel realm, String id, String clientId) {
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? clientStore.getKeyConvertor().yieldNewUniqueKey() : clientStore.getKeyConvertor().fromString(id);
if (clientId == null) {
clientId = entityId.toString();
@ -153,7 +155,7 @@ public class MapClientProvider implements ClientProvider {
LOG.tracef("addClient(%s, %s, %s)%s", realm, id, clientId, getShortStackTrace());
MapClientEntity entity = new MapClientEntity(entityId, realm.getId());
MapClientEntity<K> entity = new MapClientEntity<>(entityId, realm.getId());
entity.setClientId(clientId);
entity.setEnabled(true);
entity.setStandardFlowEnabled(true);
@ -213,7 +215,7 @@ public class MapClientProvider implements ClientProvider {
});
// TODO: ^^^^^^^ Up to here
tx.delete(UUID.fromString(id));
tx.delete(clientStore.getKeyConvertor().fromString(id));
return true;
}
@ -234,7 +236,7 @@ public class MapClientProvider implements ClientProvider {
LOG.tracef("getClientById(%s, %s)%s", realm, id, getShortStackTrace());
MapClientEntity entity = tx.read(UUID.fromString(id));
MapClientEntity<K> entity = tx.read(clientStore.getKeyConvertor().fromStringSafe(id));
return (entity == null || ! entityRealmFilter(realm).test(entity))
? null
: entityToAdapterFunc(realm).apply(entity);
@ -268,7 +270,7 @@ public class MapClientProvider implements ClientProvider {
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.CLIENT_ID, Operator.ILIKE, "%" + clientId + "%");
Stream<MapClientEntity> s = tx.getUpdatedNotRemoved(mcb)
Stream<MapClientEntity<K>> s = tx.getUpdatedNotRemoved(mcb)
.sorted(COMPARE_BY_CLIENT_ID);
return paginatedStream(s, firstResult, maxResults).map(entityToAdapterFunc(realm));
@ -276,7 +278,8 @@ public class MapClientProvider implements ClientProvider {
@Override
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) {
MapClientEntity entity = tx.read(UUID.fromString(client.getId()));
final String id = client.getId();
MapClientEntity<K> entity = tx.read(clientStore.getKeyConvertor().fromString(id));
if (entity == null) return;
@ -295,7 +298,8 @@ public class MapClientProvider implements ClientProvider {
@Override
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) {
MapClientEntity entity = tx.read(UUID.fromString(client.getId()));
final String id = client.getId();
MapClientEntity<K> entity = tx.read(clientStore.getKeyConvertor().fromString(id));
if (entity == null) return;
@ -306,7 +310,8 @@ public class MapClientProvider implements ClientProvider {
@Override
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
MapClientEntity entity = tx.read(UUID.fromString(client.getId()));
final String id = client.getId();
MapClientEntity<K> entity = tx.read(clientStore.getKeyConvertor().fromString(id));
if (entity == null) return null;
@ -326,7 +331,7 @@ public class MapClientProvider implements ClientProvider {
ModelCriteriaBuilder<ClientModel> mcb = clientStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.SCOPE_MAPPING_ROLE, Operator.EQ, role.getId());
try (Stream<MapClientEntity> toRemove = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapClientEntity<K>> toRemove = tx.getUpdatedNotRemoved(mcb)) {
toRemove
.map(clientEntity -> session.clients().getClientById(realm, clientEntity.getId().toString()))
.filter(Objects::nonNull)

View file

@ -26,40 +26,34 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.models.RoleModel;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
*
* @author hmlnarik
*/
public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientProvider> implements ClientProviderFactory, ProviderEventListener {
public class MapClientProviderFactory<K> extends AbstractMapProviderFactory<ClientProvider, K, MapClientEntity<K>, ClientModel> implements ClientProviderFactory, ProviderEventListener {
private final ConcurrentHashMap<UUID, ConcurrentMap<String, Integer>> REGISTERED_NODES_STORE = new ConcurrentHashMap<>();
private MapStorage<UUID, MapClientEntity, ClientModel> store;
private final ConcurrentHashMap<K, ConcurrentMap<String, Integer>> REGISTERED_NODES_STORE = new ConcurrentHashMap<>();
private Runnable onClose;
public MapClientProviderFactory() {
super(MapClientEntity.class, ClientModel.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("clients", UUID.class, MapClientEntity.class, ClientModel.class);
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public MapClientProvider create(KeycloakSession session) {
return new MapClientProvider(session, store, REGISTERED_NODES_STORE);
return new MapClientProvider<>(session, getStorage(session), REGISTERED_NODES_STORE);
}
@Override
@ -68,6 +62,11 @@ public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientP
onClose.run();
}
@Override
public String getHelpText() {
return "Client provider";
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof RoleContainerModel.RoleRemovedEvent) {

View file

@ -1,170 +0,0 @@
/*
* Copyright 2021 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.clientscope;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.map.common.AbstractEntity;
public abstract class AbstractClientScopeEntity<K> implements AbstractEntity<K> {
private final K id;
private final String realmId;
private String name;
private String protocol;
private String description;
private final Set<String> scopeMappings = new LinkedHashSet<>();
private final Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
private final Map<String, String> attributes = new HashMap<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected AbstractClientScopeEntity() {
this.id = null;
this.realmId = null;
}
public AbstractClientScopeEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.updated |= ! Objects.equals(this.protocol, protocol);
this.protocol = protocol;
}
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes.clear();
this.attributes.putAll(attributes);
}
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
Objects.requireNonNull(model.getId(), "protocolMapper.id");
updated = true;
this.protocolMappers.put(model.getId(), model);
return model;
}
public Stream<ProtocolMapperModel> getProtocolMappers() {
return protocolMappers.values().stream();
}
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
updated = true;
protocolMappers.put(id, mapping);
}
public void removeProtocolMapper(String id) {
updated |= protocolMappers.remove(id) != null;
}
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
this.protocolMappers.clear();
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
}
public ProtocolMapperModel getProtocolMapperById(String id) {
return id == null ? null : protocolMappers.get(id);
}
public void setAttribute(String name, String value) {
this.updated = true;
this.attributes.put(name, value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getAttribute(String name) {
return this.attributes.get(name);
}
public String getRealmId() {
return this.realmId;
}
public Stream<String> getScopeMappings() {
return scopeMappings.stream();
}
public void addScopeMapping(String id) {
if (id != null) {
updated = true;
scopeMappings.add(id);
}
}
public void deleteScopeMapping(String id) {
updated |= scopeMappings.remove(id);
}
}

View file

@ -28,17 +28,12 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RoleUtils;
public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientScopeEntity> implements ClientScopeModel {
public abstract class MapClientScopeAdapter<K> extends AbstractClientScopeModel<MapClientScopeEntity<K>> implements ClientScopeModel {
public MapClientScopeAdapter(KeycloakSession session, RealmModel realm, MapClientScopeEntity entity) {
public MapClientScopeAdapter(KeycloakSession session, RealmModel realm, MapClientScopeEntity<K> entity) {
super(session, realm, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getName() {
return entity.getName();

View file

@ -16,16 +16,155 @@
*/
package org.keycloak.models.map.clientscope;
import java.util.UUID;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.map.common.AbstractEntity;
public class MapClientScopeEntity extends AbstractClientScopeEntity<UUID> {
public class MapClientScopeEntity<K> implements AbstractEntity<K> {
private MapClientScopeEntity() {
super();
private final K id;
private final String realmId;
private String name;
private String protocol;
private String description;
private final Set<String> scopeMappings = new LinkedHashSet<>();
private final Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
private final Map<String, String> attributes = new HashMap<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected MapClientScopeEntity() {
this.id = null;
this.realmId = null;
}
public MapClientScopeEntity(UUID id, String realmId) {
super(id, realmId);
public MapClientScopeEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.updated |= ! Objects.equals(this.protocol, protocol);
this.protocol = protocol;
}
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes.clear();
this.attributes.putAll(attributes);
}
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
Objects.requireNonNull(model.getId(), "protocolMapper.id");
updated = true;
this.protocolMappers.put(model.getId(), model);
return model;
}
public Stream<ProtocolMapperModel> getProtocolMappers() {
return protocolMappers.values().stream();
}
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
updated = true;
protocolMappers.put(id, mapping);
}
public void removeProtocolMapper(String id) {
updated |= protocolMappers.remove(id) != null;
}
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
this.protocolMappers.clear();
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
}
public ProtocolMapperModel getProtocolMapperById(String id) {
return id == null ? null : protocolMappers.get(id);
}
public void setAttribute(String name, String value) {
this.updated = true;
this.attributes.put(name, value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getAttribute(String name) {
return this.attributes.get(name);
}
public String getRealmId() {
return this.realmId;
}
public Stream<String> getScopeMappings() {
return scopeMappings.stream();
}
public void addScopeMapping(String id) {
if (id != null) {
updated = true;
scopeMappings.add(id);
}
}
public void deleteScopeMapping(String id) {
updated |= scopeMappings.remove(id);
}
}

View file

@ -19,7 +19,6 @@ package org.keycloak.models.map.clientscope;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -40,38 +39,42 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.utils.KeycloakModelUtils;
public class MapClientScopeProvider implements ClientScopeProvider {
public class MapClientScopeProvider<K> implements ClientScopeProvider {
private static final Logger LOG = Logger.getLogger(MapClientScopeProvider.class);
private static final Predicate<MapClientScopeEntity> ALWAYS_FALSE = c -> { return false; };
private final KeycloakSession session;
private final MapKeycloakTransaction<UUID, MapClientScopeEntity, ClientScopeModel> tx;
private final MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore;
private final MapKeycloakTransaction<K, MapClientScopeEntity<K>, ClientScopeModel> tx;
private final MapStorage<K, MapClientScopeEntity<K>, ClientScopeModel> clientScopeStore;
private static final Comparator<MapClientScopeEntity> COMPARE_BY_NAME = Comparator.comparing(MapClientScopeEntity::getName);
public MapClientScopeProvider(KeycloakSession session, MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore) {
public MapClientScopeProvider(KeycloakSession session, MapStorage<K, MapClientScopeEntity<K>, ClientScopeModel> clientScopeStore) {
this.session = session;
this.clientScopeStore = clientScopeStore;
this.tx = clientScopeStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private MapClientScopeEntity registerEntityForChanges(MapClientScopeEntity origEntity) {
final MapClientScopeEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapClientScopeEntity::isUpdated);
private MapClientScopeEntity<K> registerEntityForChanges(MapClientScopeEntity<K> origEntity) {
final MapClientScopeEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapClientScopeEntity<K>::isUpdated);
return res;
}
private Function<MapClientScopeEntity, ClientScopeModel> entityToAdapterFunc(RealmModel realm) {
private Function<MapClientScopeEntity<K>, ClientScopeModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapClientScopeAdapter(session, realm, registerEntityForChanges(origEntity));
return origEntity -> new MapClientScopeAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return clientScopeStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private Predicate<MapClientScopeEntity> entityRealmFilter(RealmModel realm) {
private Predicate<MapClientScopeEntity<K>> entityRealmFilter(RealmModel realm) {
if (realm == null || realm.getId() == null) {
return MapClientScopeProvider.ALWAYS_FALSE;
return c -> false;
}
String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId());
@ -98,11 +101,11 @@ public class MapClientScopeProvider implements ClientScopeProvider {
throw new ModelDuplicateException("Client scope with name '" + name + "' in realm " + realm.getName());
}
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? clientScopeStore.getKeyConvertor().yieldNewUniqueKey() : clientScopeStore.getKeyConvertor().fromString(id);
LOG.tracef("addClientScope(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
MapClientScopeEntity entity = new MapClientScopeEntity(entityId, realm.getId());
MapClientScopeEntity<K> entity = new MapClientScopeEntity<>(entityId, realm.getId());
entity.setName(KeycloakModelUtils.convertClientScopeName(name));
if (tx.read(entity.getId()) != null) {
throw new ModelDuplicateException("Client scope exists: " + id);
@ -137,7 +140,7 @@ public class MapClientScopeProvider implements ClientScopeProvider {
}
});
tx.delete(UUID.fromString(id));
tx.delete(clientScopeStore.getKeyConvertor().fromString(id));
return true;
}
@ -159,14 +162,14 @@ public class MapClientScopeProvider implements ClientScopeProvider {
LOG.tracef("getClientScopeById(%s, %s)%s", realm, id, getShortStackTrace());
UUID uuid;
K uuid;
try {
uuid = UUID.fromString(id);
uuid = clientScopeStore.getKeyConvertor().fromStringSafe(id);
} catch (IllegalArgumentException ex) {
return null;
}
MapClientScopeEntity entity = tx.read(uuid);
MapClientScopeEntity<K> entity = tx.read(uuid);
return (entity == null || ! entityRealmFilter(realm).test(entity))
? null
: entityToAdapterFunc(realm).apply(entity);

View file

@ -16,28 +16,25 @@
*/
package org.keycloak.models.map.clientscope;
import java.util.UUID;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientScopeProvider;
import org.keycloak.models.ClientScopeProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
public class MapClientScopeProviderFactory extends AbstractMapProviderFactory<ClientScopeProvider> implements ClientScopeProviderFactory {
public class MapClientScopeProviderFactory<K> extends AbstractMapProviderFactory<ClientScopeProvider, K, MapClientScopeEntity<K>, ClientScopeModel> implements ClientScopeProviderFactory {
private MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> store;
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("clientscope", UUID.class, MapClientScopeEntity.class, ClientScopeModel.class);
public MapClientScopeProviderFactory() {
super(MapClientScopeEntity.class, ClientScopeModel.class);
}
@Override
public ClientScopeProvider create(KeycloakSession session) {
return new MapClientScopeProvider(session, store);
return new MapClientScopeProvider<>(session, getStorage(session));
}
@Override
public String getHelpText() {
return "Client scope provider";
}
}

View file

@ -17,23 +17,52 @@
package org.keycloak.models.map.common;
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
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.component.AmphibianProviderFactory;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.jboss.logging.Logger;
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
/**
*
* @author hmlnarik
*/
public abstract class AbstractMapProviderFactory<T extends Provider> implements ProviderFactory<T> {
public abstract class AbstractMapProviderFactory<T extends Provider, K, V extends AbstractEntity<K>, M> implements AmphibianProviderFactory<T> {
public static final String PROVIDER_ID = "map";
public static final String CONFIG_STORAGE = "storage";
protected final Logger LOG = Logger.getLogger(getClass());
protected final Class<M> modelType;
protected final Class<V> entityType;
private Scope storageConfigScope;
@SuppressWarnings("unchecked")
protected AbstractMapProviderFactory(Class<? extends AbstractEntity> entityType, Class<M> modelType) {
this.modelType = modelType;
this.entityType = (Class<V>) entityType;
}
@Override
public void init(Scope config) {
public String getId() {
return PROVIDER_ID;
}
protected MapStorage<K, V, M> getStorage(KeycloakSession session) {
MapStorageProviderFactory storageProviderFactory = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
MapStorageProvider.class, storageConfigScope, MapStorageSpi.NAME);
final MapStorageProvider factory = storageProviderFactory.create(session);
return factory.getStorage(entityType, modelType);
}
@Override
@ -41,11 +70,8 @@ public abstract class AbstractMapProviderFactory<T extends Provider> implements
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
public void init(Scope config) {
// Implementation of the map storage SPI
this.storageConfigScope = config.scope(CONFIG_STORAGE);
}
}

View file

@ -19,6 +19,9 @@ package org.keycloak.models.map.common;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -49,7 +52,12 @@ public class Serialization {
public static final ConcurrentHashMap<Class<?>, ObjectReader> READERS = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<Class<?>, ObjectWriter> WRITERS = new ConcurrentHashMap<>();
abstract class IgnoreUpdatedMixIn { @JsonIgnore public abstract boolean isUpdated(); }
abstract class IgnoreUpdatedMixIn {
@JsonIgnore public abstract boolean isUpdated();
@JsonTypeInfo(use=Id.CLASS, include=As.WRAPPER_ARRAY)
abstract Object getId();
}
static {
JavaType type = TypeFactory.unknownType();

View file

@ -1,134 +0,0 @@
/*
* Copyright 2020 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.group;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
*
* @author mhajas
*/
public abstract class AbstractGroupEntity<K> implements AbstractEntity<K> {
private final K id;
private final String realmId;
private String name;
private String parentId;
private Map<String, List<String>> attributes = new HashMap<>();
private Set<String> grantedRoles = new HashSet<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected AbstractGroupEntity() {
this.id = null;
this.realmId = null;
}
public AbstractGroupEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.updated |= !Objects.equals(this.parentId, parentId);
this.parentId = parentId;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> value) {
this.updated |= !this.attributes.containsKey(name) || !this.attributes.get(name).equals(value);
this.attributes.put(name, value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public List<String> getAttribute(String name) {
return this.attributes.get(name);
}
public String getRealmId() {
return this.realmId;
}
public Set<String> getGrantedRoles() {
return grantedRoles;
}
public void setGrantedRoles(Set<String> grantedRoles) {
this.updated |= !Objects.equals(this.grantedRoles, grantedRoles);
this.grantedRoles = grantedRoles;
}
public void removeRole(String role) {
this.updated |= this.grantedRoles.contains(role);
this.grantedRoles.remove(role);
}
public void addGrantedRole(String role) {
this.updated |= !this.grantedRoles.contains(role);
this.grantedRoles.add(role);
}
}

View file

@ -29,16 +29,11 @@ import java.util.Map;
import java.util.stream.Stream;
public class MapGroupAdapter extends AbstractGroupModel<MapGroupEntity> {
public MapGroupAdapter(KeycloakSession session, RealmModel realm, MapGroupEntity entity) {
public abstract class MapGroupAdapter<K> extends AbstractGroupModel<MapGroupEntity<K>> {
public MapGroupAdapter(KeycloakSession session, RealmModel realm, MapGroupEntity<K> entity) {
super(session, realm, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getName() {
return entity.getName();

View file

@ -17,15 +17,118 @@
package org.keycloak.models.map.group;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractEntity;
public class MapGroupEntity extends AbstractGroupEntity<UUID> {
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
*
* @author mhajas
*/
public class MapGroupEntity<K> implements AbstractEntity<K> {
private final K id;
private final String realmId;
private String name;
private String parentId;
private Map<String, List<String>> attributes = new HashMap<>();
private Set<String> grantedRoles = new HashSet<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected MapGroupEntity() {
super();
this.id = null;
this.realmId = null;
}
public MapGroupEntity(UUID id, String realmId) {
super(id, realmId);
public MapGroupEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.updated |= !Objects.equals(this.parentId, parentId);
this.parentId = parentId;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> value) {
this.updated |= !this.attributes.containsKey(name) || !this.attributes.get(name).equals(value);
this.attributes.put(name, value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public List<String> getAttribute(String name) {
return this.attributes.get(name);
}
public String getRealmId() {
return this.realmId;
}
public Set<String> getGrantedRoles() {
return grantedRoles;
}
public void setGrantedRoles(Set<String> grantedRoles) {
this.updated |= !Objects.equals(this.grantedRoles, grantedRoles);
this.grantedRoles = grantedRoles;
}
public void removeRole(String role) {
this.updated |= this.grantedRoles.contains(role);
this.grantedRoles.remove(role);
}
public void addGrantedRole(String role) {
this.updated |= !this.grantedRoles.contains(role);
this.grantedRoles.add(role);
}
}

View file

@ -33,7 +33,6 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
@ -41,29 +40,34 @@ import java.util.stream.Stream;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapGroupProvider implements GroupProvider {
public class MapGroupProvider<K> implements GroupProvider {
private static final Logger LOG = Logger.getLogger(MapGroupProvider.class);
private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapGroupEntity, GroupModel> tx;
private final MapStorage<UUID, MapGroupEntity, GroupModel> groupStore;
final MapKeycloakTransaction<K, MapGroupEntity<K>, GroupModel> tx;
private final MapStorage<K, MapGroupEntity<K>, GroupModel> groupStore;
public MapGroupProvider(KeycloakSession session, MapStorage<UUID, MapGroupEntity, GroupModel> groupStore) {
public MapGroupProvider(KeycloakSession session, MapStorage<K, MapGroupEntity<K>, GroupModel> groupStore) {
this.session = session;
this.groupStore = groupStore;
this.tx = groupStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private MapGroupEntity registerEntityForChanges(MapGroupEntity origEntity) {
final MapGroupEntity res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapGroupEntity::isUpdated);
private MapGroupEntity<K> registerEntityForChanges(MapGroupEntity<K> origEntity) {
final MapGroupEntity<K> res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapGroupEntity<K>::isUpdated);
return res;
}
private Function<MapGroupEntity, GroupModel> entityToAdapterFunc(RealmModel realm) {
private Function<MapGroupEntity<K>, GroupModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapGroupAdapter(session, realm, registerEntityForChanges(origEntity));
return origEntity -> new MapGroupAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return groupStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
@Override
@ -74,15 +78,14 @@ public class MapGroupProvider implements GroupProvider {
LOG.tracef("getGroupById(%s, %s)%s", realm, id, getShortStackTrace());
UUID uid;
K uid;
try {
uid = UUID.fromString(id);
uid = groupStore.getKeyConvertor().fromStringSafe(id);
} catch (IllegalArgumentException ex) {
return null;
}
MapGroupEntity entity = tx.read(uid);
MapGroupEntity<K> entity = tx.read(uid);
String realmId = realm.getId();
return (entity == null || ! Objects.equals(realmId, entity.getRealmId()))
? null
@ -112,7 +115,7 @@ public class MapGroupProvider implements GroupProvider {
@Override
public Stream<GroupModel> getGroupsStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
ModelCriteriaBuilder<GroupModel> mcb = groupStore.createCriteriaBuilder()
.compare(SearchableFields.ID, Operator.IN, ids.map(UUID::fromString))
.compare(SearchableFields.ID, Operator.IN, ids.map(groupStore.getKeyConvertor()::fromString))
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
if (search != null) {
@ -188,7 +191,7 @@ public class MapGroupProvider implements GroupProvider {
@Override
public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) {
LOG.tracef("createGroup(%s, %s, %s, %s)%s", realm, id, name, toParent, getShortStackTrace());
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? groupStore.getKeyConvertor().yieldNewUniqueKey() : groupStore.getKeyConvertor().fromString(id);
// Check Db constraint: uniqueConstraints = { @UniqueConstraint(columnNames = {"REALM_ID", "PARENT_GROUP", "NAME"})}
String parentId = toParent == null ? null : toParent.getId();
@ -201,7 +204,7 @@ public class MapGroupProvider implements GroupProvider {
throw new ModelDuplicateException("Group with name '" + name + "' in realm " + realm.getName() + " already exists for requested parent" );
}
MapGroupEntity entity = new MapGroupEntity(entityId, realm.getId());
MapGroupEntity<K> entity = new MapGroupEntity<K>(entityId, realm.getId());
entity.setName(name);
entity.setParentId(toParent == null ? null : toParent.getId());
if (tx.read(entity.getId()) != null) {
@ -243,7 +246,7 @@ public class MapGroupProvider implements GroupProvider {
// TODO: ^^^^^^^ Up to here
tx.delete(UUID.fromString(group.getId()));
tx.delete(groupStore.getKeyConvertor().fromString(group.getId()));
return true;
}
@ -264,7 +267,7 @@ public class MapGroupProvider implements GroupProvider {
.compare(SearchableFields.PARENT_ID, Operator.EQ, parentId)
.compare(SearchableFields.NAME, Operator.EQ, group.getName());
try (Stream<MapGroupEntity> possibleSiblings = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapGroupEntity<K>> possibleSiblings = tx.getUpdatedNotRemoved(mcb)) {
if (possibleSiblings.findAny().isPresent()) {
throw new ModelDuplicateException("Parent already contains subgroup named '" + group.getName() + "'");
}
@ -286,7 +289,7 @@ public class MapGroupProvider implements GroupProvider {
.compare(SearchableFields.PARENT_ID, Operator.EQ, (Object) null)
.compare(SearchableFields.NAME, Operator.EQ, subGroup.getName());
try (Stream<MapGroupEntity> possibleSiblings = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapGroupEntity<K>> possibleSiblings = tx.getUpdatedNotRemoved(mcb)) {
if (possibleSiblings.findAny().isPresent()) {
throw new ModelDuplicateException("There is already a top level group named '" + subGroup.getName() + "'");
}
@ -300,7 +303,7 @@ public class MapGroupProvider implements GroupProvider {
ModelCriteriaBuilder<GroupModel> mcb = groupStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId());
try (Stream<MapGroupEntity> toRemove = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapGroupEntity<K>> toRemove = tx.getUpdatedNotRemoved(mcb)) {
toRemove
.map(groupEntity -> session.groups().getGroupById(realm, groupEntity.getId().toString()))
.forEach(groupModel -> groupModel.deleteRoleMapping(role));

View file

@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.group;
import org.keycloak.models.ClientModel;
@ -27,38 +26,32 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
/**
*
* @author mhajas
*/
public class MapGroupProviderFactory extends AbstractMapProviderFactory<GroupProvider> implements GroupProviderFactory, ProviderEventListener {
private MapStorage<UUID, MapGroupEntity, GroupModel> store;
public class MapGroupProviderFactory<K> extends AbstractMapProviderFactory<GroupProvider, K, MapGroupEntity<K>, GroupModel> implements GroupProviderFactory, ProviderEventListener {
private Runnable onClose;
public MapGroupProviderFactory() {
super(MapGroupEntity.class, GroupModel.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("groups", UUID.class, MapGroupEntity.class, GroupModel.class);
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public MapGroupProvider create(KeycloakSession session) {
return new MapGroupProvider(session, store);
return new MapGroupProvider<>(session, getStorage(session));
}
@Override
@ -67,6 +60,11 @@ public class MapGroupProviderFactory extends AbstractMapProviderFactory<GroupPro
onClose.run();
}
@Override
public String getHelpText() {
return "Group provider";
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof RoleContainerModel.RoleRemovedEvent) {

View file

@ -1,129 +0,0 @@
/*
* Copyright 2021 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.loginFailure;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractUserLoginFailureEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String userId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private int failedLoginNotBefore;
private int numFailures;
private long lastFailure;
private String lastIPFailure;
public AbstractUserLoginFailureEntity() {
this.id = null;
this.realmId = null;
this.userId = null;
}
public AbstractUserLoginFailureEntity(K id, String realmId, String userId) {
this.id = id;
this.realmId = realmId;
this.userId = userId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.updated |= !Objects.equals(this.userId, userId);
this.userId = userId;
}
public int getFailedLoginNotBefore() {
return failedLoginNotBefore;
}
public void setFailedLoginNotBefore(int failedLoginNotBefore) {
this.updated |= this.failedLoginNotBefore != failedLoginNotBefore;
this.failedLoginNotBefore = failedLoginNotBefore;
}
public int getNumFailures() {
return numFailures;
}
public void setNumFailures(int numFailures) {
this.updated |= this.numFailures != numFailures;
this.numFailures = numFailures;
}
public long getLastFailure() {
return lastFailure;
}
public void setLastFailure(long lastFailure) {
this.updated |= this.lastFailure != lastFailure;
this.lastFailure = lastFailure;
}
public String getLastIPFailure() {
return lastIPFailure;
}
public void setLastIPFailure(String lastIPFailure) {
this.updated |= !Objects.equals(this.lastIPFailure, lastIPFailure);
this.lastIPFailure = lastIPFailure;
}
public void clearFailures() {
this.updated |= this.failedLoginNotBefore != 0 || this.numFailures != 0 ||
this.lastFailure != 0l || this.lastIPFailure != null;
this.failedLoginNotBefore = this.numFailures = 0;
this.lastFailure = 0l;
this.lastIPFailure = null;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
}

View file

@ -22,8 +22,8 @@ import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserLoginFailureAdapter extends AbstractUserLoginFailureModel<MapUserLoginFailureEntity> {
public MapUserLoginFailureAdapter(KeycloakSession session, RealmModel realm, MapUserLoginFailureEntity entity) {
public abstract class MapUserLoginFailureAdapter<K> extends AbstractUserLoginFailureModel<MapUserLoginFailureEntity<K>> {
public MapUserLoginFailureAdapter(KeycloakSession session, RealmModel realm, MapUserLoginFailureEntity<K> entity) {
super(session, realm, entity);
}

View file

@ -16,17 +16,114 @@
*/
package org.keycloak.models.map.loginFailure;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserLoginFailureEntity extends AbstractUserLoginFailureEntity<UUID> {
protected MapUserLoginFailureEntity() {
super();
public class MapUserLoginFailureEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String userId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private int failedLoginNotBefore;
private int numFailures;
private long lastFailure;
private String lastIPFailure;
public MapUserLoginFailureEntity() {
this.id = null;
this.realmId = null;
this.userId = null;
}
public MapUserLoginFailureEntity(UUID id, String realmId, String userId) {
super(id, realmId, userId);
public MapUserLoginFailureEntity(K id, String realmId, String userId) {
this.id = id;
this.realmId = realmId;
this.userId = userId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.updated |= !Objects.equals(this.userId, userId);
this.userId = userId;
}
public int getFailedLoginNotBefore() {
return failedLoginNotBefore;
}
public void setFailedLoginNotBefore(int failedLoginNotBefore) {
this.updated |= this.failedLoginNotBefore != failedLoginNotBefore;
this.failedLoginNotBefore = failedLoginNotBefore;
}
public int getNumFailures() {
return numFailures;
}
public void setNumFailures(int numFailures) {
this.updated |= this.numFailures != numFailures;
this.numFailures = numFailures;
}
public long getLastFailure() {
return lastFailure;
}
public void setLastFailure(long lastFailure) {
this.updated |= this.lastFailure != lastFailure;
this.lastFailure = lastFailure;
}
public String getLastIPFailure() {
return lastIPFailure;
}
public void setLastIPFailure(String lastIPFailure) {
this.updated |= !Objects.equals(this.lastIPFailure, lastIPFailure);
this.lastIPFailure = lastIPFailure;
}
public void clearFailures() {
this.updated |= this.failedLoginNotBefore != 0 || this.numFailures != 0 ||
this.lastFailure != 0l || this.lastIPFailure != null;
this.failedLoginNotBefore = this.numFailures = 0;
this.lastFailure = 0l;
this.lastIPFailure = null;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
}

View file

@ -26,7 +26,6 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import java.util.UUID;
import java.util.function.Function;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
@ -34,14 +33,14 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
public class MapUserLoginFailureProvider<K> implements UserLoginFailureProvider {
private static final Logger LOG = Logger.getLogger(MapUserLoginFailureProvider.class);
private final KeycloakSession session;
protected final MapKeycloakTransaction<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureTx;
private final MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore;
protected final MapKeycloakTransaction<K, MapUserLoginFailureEntity<K>, UserLoginFailureModel> userLoginFailureTx;
private final MapStorage<K, MapUserLoginFailureEntity<K>, UserLoginFailureModel> userLoginFailureStore;
public MapUserLoginFailureProvider(KeycloakSession session, MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore) {
public MapUserLoginFailureProvider(KeycloakSession session, MapStorage<K, MapUserLoginFailureEntity<K>, UserLoginFailureModel> userLoginFailureStore) {
this.session = session;
this.userLoginFailureStore = userLoginFailureStore;
@ -49,14 +48,19 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
session.getTransactionManager().enlistAfterCompletion(userLoginFailureTx);
}
private Function<MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureEntityToAdapterFunc(RealmModel realm) {
private Function<MapUserLoginFailureEntity<K>, UserLoginFailureModel> userLoginFailureEntityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapUserLoginFailureAdapter(session, realm, registerEntityForChanges(origEntity));
return origEntity -> new MapUserLoginFailureAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return userLoginFailureStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private MapUserLoginFailureEntity registerEntityForChanges(MapUserLoginFailureEntity origEntity) {
MapUserLoginFailureEntity res = userLoginFailureTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
userLoginFailureTx.updateIfChanged(origEntity.getId(), res, MapUserLoginFailureEntity::isUpdated);
private MapUserLoginFailureEntity<K> registerEntityForChanges(MapUserLoginFailureEntity<K> origEntity) {
MapUserLoginFailureEntity<K> res = userLoginFailureTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
userLoginFailureTx.updateIfChanged(origEntity.getId(), res, MapUserLoginFailureEntity<K>::isUpdated);
return res;
}
@ -82,10 +86,10 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
LOG.tracef("addUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
MapUserLoginFailureEntity userLoginFailureEntity = userLoginFailureTx.getUpdatedNotRemoved(mcb).findFirst().orElse(null);
MapUserLoginFailureEntity<K> userLoginFailureEntity = userLoginFailureTx.getUpdatedNotRemoved(mcb).findFirst().orElse(null);
if (userLoginFailureEntity == null) {
userLoginFailureEntity = new MapUserLoginFailureEntity(UUID.randomUUID(), realm.getId(), userId);
userLoginFailureEntity = new MapUserLoginFailureEntity<>(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), realm.getId(), userId);
userLoginFailureTx.create(userLoginFailureEntity.getId(), userLoginFailureEntity);
}
@ -101,7 +105,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
LOG.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
userLoginFailureTx.delete(UUID.randomUUID(), mcb);
userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override
@ -111,7 +115,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
LOG.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace());
userLoginFailureTx.delete(UUID.randomUUID(), mcb);
userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override

View file

@ -23,46 +23,58 @@ import org.keycloak.models.UserLoginFailureProvider;
import org.keycloak.models.UserLoginFailureProviderFactory;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import java.util.UUID;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFactory<UserLoginFailureProvider>
implements UserLoginFailureProviderFactory {
public class MapUserLoginFailureProviderFactory<K> extends AbstractMapProviderFactory<UserLoginFailureProvider, K, MapUserLoginFailureEntity<K>, UserLoginFailureModel>
implements UserLoginFailureProviderFactory, ProviderEventListener {
private MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore;
private Runnable onClose;
public MapUserLoginFailureProviderFactory() {
super(MapUserLoginFailureEntity.class, UserLoginFailureModel.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
userLoginFailureStore = sp.getStorage("userLoginFailures", UUID.class, MapUserLoginFailureEntity.class, UserLoginFailureModel.class);
factory.register(this);
onClose = () -> factory.unregister(this);
}
factory.register(event -> {
@Override
public void close() {
super.close();
onClose.run();
}
@Override
public MapUserLoginFailureProvider<K> create(KeycloakSession session) {
return new MapUserLoginFailureProvider<>(session, getStorage(session));
}
@Override
public String getHelpText() {
return "User login failure provider";
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof UserModel.UserRemovedEvent) {
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(userRemovedEvent.getKeycloakSession());
provider.removeUserLoginFailure(userRemovedEvent.getRealm(), userRemovedEvent.getUser().getId());
}
});
factory.register(event -> {
if (event instanceof RealmModel.RealmRemovedEvent) {
} else if (event instanceof RealmModel.RealmRemovedEvent) {
RealmModel.RealmRemovedEvent realmRemovedEvent = (RealmModel.RealmRemovedEvent) event;
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(realmRemovedEvent.getKeycloakSession());
provider.removeAllUserLoginFailures(realmRemovedEvent.getRealm());
}
});
}
@Override
public MapUserLoginFailureProvider create(KeycloakSession session) {
return new MapUserLoginFailureProvider(session, userLoginFailureStore);
}
}

View file

@ -60,7 +60,7 @@ import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.utils.ComponentUtil;
public class MapRealmAdapter extends AbstractRealmModel<MapRealmEntity> implements RealmModel {
public abstract class MapRealmAdapter<K> extends AbstractRealmModel<MapRealmEntity<K>> implements RealmModel {
private static final String ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN = "actionTokenGeneratedByUserLifespan";
private static final String DEFAULT_SIGNATURE_ALGORITHM = "defaultSignatureAlgorithm";
@ -75,15 +75,10 @@ public class MapRealmAdapter extends AbstractRealmModel<MapRealmEntity> implemen
private PasswordPolicy passwordPolicy;
public MapRealmAdapter(KeycloakSession session, MapRealmEntity entity) {
public MapRealmAdapter(KeycloakSession session, MapRealmEntity<K> entity) {
super(session, entity);
}
@Override
public String getId() {
return entity.getId();
}
@Override
public String getName() {
return entity.getName();

View file

@ -39,29 +39,34 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.utils.KeycloakModelUtils;
public class MapRealmProvider implements RealmProvider {
public class MapRealmProvider<K> implements RealmProvider {
private static final Logger LOG = Logger.getLogger(MapRealmProvider.class);
private final KeycloakSession session;
final MapKeycloakTransaction<String, MapRealmEntity, RealmModel> tx;
private final MapStorage<String, MapRealmEntity, RealmModel> realmStore;
final MapKeycloakTransaction<K, MapRealmEntity<K>, RealmModel> tx;
private final MapStorage<K, MapRealmEntity<K>, RealmModel> realmStore;
public MapRealmProvider(KeycloakSession session, MapStorage<String, MapRealmEntity, RealmModel> realmStore) {
public MapRealmProvider(KeycloakSession session, MapStorage<K, MapRealmEntity<K>, RealmModel> realmStore) {
this.session = session;
this.realmStore = realmStore;
this.tx = realmStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private RealmModel entityToAdapter(MapRealmEntity entity) {
private RealmModel entityToAdapter(MapRealmEntity<K> entity) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return new MapRealmAdapter(session, registerEntityForChanges(entity));
return new MapRealmAdapter<K>(session, registerEntityForChanges(entity)) {
@Override
public String getId() {
return realmStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private MapRealmEntity registerEntityForChanges(MapRealmEntity origEntity) {
final MapRealmEntity res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapRealmEntity::isUpdated);
private MapRealmEntity<K> registerEntityForChanges(MapRealmEntity<K> origEntity) {
final MapRealmEntity<K> res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapRealmEntity<K>::isUpdated);
return res;
}
@ -76,20 +81,21 @@ public class MapRealmProvider implements RealmProvider {
throw new ModelDuplicateException("Realm with given name exists: " + name);
}
if (id != null) {
if (tx.read(id) != null) {
throw new ModelDuplicateException("Realm exists: " + id);
K kId = id == null ? null : realmStore.getKeyConvertor().fromString(id);
if (kId != null) {
if (tx.read(kId) != null) {
throw new ModelDuplicateException("Realm exists: " + kId);
}
} else {
id = KeycloakModelUtils.generateId();
kId = realmStore.getKeyConvertor().yieldNewUniqueKey();
}
LOG.tracef("createRealm(%s, %s)%s", id, name, getShortStackTrace());
LOG.tracef("createRealm(%s, %s)%s", kId, name, getShortStackTrace());
MapRealmEntity entity = new MapRealmEntity(id);
MapRealmEntity<K> entity = new MapRealmEntity<>(kId);
entity.setName(name);
tx.create(id, entity);
tx.create(kId, entity);
return entityToAdapter(entity);
}
@ -99,7 +105,7 @@ public class MapRealmProvider implements RealmProvider {
LOG.tracef("getRealm(%s)%s", id, getShortStackTrace());
MapRealmEntity entity = tx.read(id);
MapRealmEntity<K> entity = tx.read(realmStore.getKeyConvertor().fromStringSafe(id));
return entity == null ? null : entityToAdapter(entity);
}
@ -112,12 +118,12 @@ public class MapRealmProvider implements RealmProvider {
ModelCriteriaBuilder<RealmModel> mcb = realmStore.createCriteriaBuilder()
.compare(SearchableFields.NAME, Operator.EQ, name);
String realmId = tx.getUpdatedNotRemoved(mcb)
K realmId = tx.getUpdatedNotRemoved(mcb)
.findFirst()
.map(MapRealmEntity::getId)
.map(MapRealmEntity<K>::getId)
.orElse(null);
//we need to go via session.realms() not to bypass cache
return realmId == null ? null : session.realms().getRealm(realmId);
return realmId == null ? null : session.realms().getRealm(realmStore.getKeyConvertor().keyToString(realmId));
}
@Override
@ -167,7 +173,7 @@ public class MapRealmProvider implements RealmProvider {
});
// TODO: ^^^^^^^ Up to here
tx.delete(id);
tx.delete(realmStore.getKeyConvertor().fromString(id));
return true;
}
@ -178,7 +184,7 @@ public class MapRealmProvider implements RealmProvider {
tx.getUpdatedNotRemoved(mcb)
.map(this::registerEntityForChanges)
.forEach(MapRealmEntity::removeExpiredClientInitialAccesses);
.forEach(MapRealmEntity<K>::removeExpiredClientInitialAccesses);
}
//TODO move the following method to adapter

View file

@ -18,27 +18,23 @@ package org.keycloak.models.map.realm;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RealmProviderFactory;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
public class MapRealmProviderFactory extends AbstractMapProviderFactory<RealmProvider> implements RealmProviderFactory {
public class MapRealmProviderFactory<K> extends AbstractMapProviderFactory<RealmProvider, K, MapRealmEntity<K>, RealmModel> implements RealmProviderFactory {
//for now we have to support String ids
private MapStorage<String, MapRealmEntity, RealmModel> store;
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("realm", String.class, MapRealmEntity.class, RealmModel.class);
public MapRealmProviderFactory() {
super(MapRealmEntity.class, RealmModel.class);
}
@Override
public RealmProvider create(KeycloakSession session) {
return new MapRealmProvider(session, store);
return new MapRealmProvider<>(session, getStorage(session));
}
@Override
public String getHelpText() {
return "Realm provider";
}
}

View file

@ -1,150 +0,0 @@
/*
* Copyright 2020 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.role;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.keycloak.models.map.common.AbstractEntity;
public abstract class AbstractRoleEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String name;
private String description;
private boolean clientRole;
private String clientId;
private Set<String> compositeRoles = new HashSet<>();
private Map<String, List<String>> attributes = new HashMap<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected AbstractRoleEntity() {
this.id = null;
this.realmId = null;
}
public AbstractRoleEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> values) {
this.updated |= ! Objects.equals(this.attributes.put(name, values), values);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= ! Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public boolean isClientRole() {
return clientRole;
}
public void setClientRole(boolean clientRole) {
this.updated |= ! Objects.equals(this.clientRole, clientRole);
this.clientRole = clientRole;
}
public boolean isComposite() {
return ! (compositeRoles == null || compositeRoles.isEmpty());
}
public Set<String> getCompositeRoles() {
return compositeRoles;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= ! Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public void setCompositeRoles(Set<String> compositeRoles) {
this.updated |= ! Objects.equals(this.compositeRoles, compositeRoles);
this.compositeRoles.clear();
this.compositeRoles.addAll(compositeRoles);
}
public void addCompositeRole(String roleId) {
this.updated |= this.compositeRoles.add(roleId);
}
public void removeCompositeRole(String roleId) {
this.updated |= this.compositeRoles.remove(roleId);
}
}

View file

@ -30,11 +30,11 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.utils.KeycloakModelUtils;
public class MapRoleAdapter extends AbstractRoleModel<MapRoleEntity> implements RoleModel {
public abstract class MapRoleAdapter<K> extends AbstractRoleModel<MapRoleEntity<K>> implements RoleModel {
private static final Logger LOG = Logger.getLogger(MapRoleAdapter.class);
public MapRoleAdapter(KeycloakSession session, RealmModel realm, MapRoleEntity entity) {
public MapRoleAdapter(KeycloakSession session, RealmModel realm, MapRoleEntity<K> entity) {
super(session, realm, entity);
}
@ -53,11 +53,6 @@ public class MapRoleAdapter extends AbstractRoleModel<MapRoleEntity> implements
entity.setDescription(description);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public void setName(String name) {
entity.setName(name);

View file

@ -16,16 +16,135 @@
*/
package org.keycloak.models.map.role;
import java.util.UUID;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.keycloak.models.map.common.AbstractEntity;
public class MapRoleEntity extends AbstractRoleEntity<UUID> {
public class MapRoleEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
private String name;
private String description;
private boolean clientRole;
private String clientId;
private Set<String> compositeRoles = new HashSet<>();
private Map<String, List<String>> attributes = new HashMap<>();
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected MapRoleEntity() {
super();
this.id = null;
this.realmId = null;
}
public MapRoleEntity(UUID id, String realmId) {
super(id, realmId);
public MapRoleEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.updated |= ! Objects.equals(this.name, name);
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.updated |= ! Objects.equals(this.description, description);
this.description = description;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= ! Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> values) {
this.updated |= ! Objects.equals(this.attributes.put(name, values), values);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= ! Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public boolean isClientRole() {
return clientRole;
}
public void setClientRole(boolean clientRole) {
this.updated |= ! Objects.equals(this.clientRole, clientRole);
this.clientRole = clientRole;
}
public boolean isComposite() {
return ! (compositeRoles == null || compositeRoles.isEmpty());
}
public Set<String> getCompositeRoles() {
return compositeRoles;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= ! Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public void setCompositeRoles(Set<String> compositeRoles) {
this.updated |= ! Objects.equals(this.compositeRoles, compositeRoles);
this.compositeRoles.clear();
this.compositeRoles.addAll(compositeRoles);
}
public void addCompositeRole(String roleId) {
this.updated |= this.compositeRoles.add(roleId);
}
public void removeCompositeRole(String roleId) {
this.updated |= this.compositeRoles.remove(roleId);
}
}

View file

@ -28,7 +28,6 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.common.Serialization;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -43,16 +42,16 @@ import org.keycloak.models.map.common.StreamUtils;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
public class MapRoleProvider implements RoleProvider {
public class MapRoleProvider<K> implements RoleProvider {
private static final Logger LOG = Logger.getLogger(MapRoleProvider.class);
private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapRoleEntity, RoleModel> tx;
private final MapStorage<UUID, MapRoleEntity, RoleModel> roleStore;
final MapKeycloakTransaction<K, MapRoleEntity<K>, RoleModel> tx;
private final MapStorage<K, MapRoleEntity<K>, RoleModel> roleStore;
private static final Comparator<MapRoleEntity> COMPARE_BY_NAME = new Comparator<MapRoleEntity>() {
private static final Comparator<MapRoleEntity<?>> COMPARE_BY_NAME = new Comparator<MapRoleEntity<?>>() {
@Override
public int compare(MapRoleEntity o1, MapRoleEntity o2) {
public int compare(MapRoleEntity<?> o1, MapRoleEntity<?> o2) {
String r1 = o1 == null ? null : o1.getName();
String r2 = o2 == null ? null : o2.getName();
return r1 == r2 ? 0
@ -63,22 +62,27 @@ public class MapRoleProvider implements RoleProvider {
}
};
public MapRoleProvider(KeycloakSession session, MapStorage<UUID, MapRoleEntity, RoleModel> roleStore) {
public MapRoleProvider(KeycloakSession session, MapStorage<K, MapRoleEntity<K>, RoleModel> roleStore) {
this.session = session;
this.roleStore = roleStore;
this.tx = roleStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private Function<MapRoleEntity, RoleModel> entityToAdapterFunc(RealmModel realm) {
private Function<MapRoleEntity<K>, RoleModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapRoleAdapter(session, realm, registerEntityForChanges(origEntity));
return origEntity -> new MapRoleAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return roleStore.getKeyConvertor().keyToString(entity.getId());
}
};
}
private MapRoleEntity registerEntityForChanges(MapRoleEntity origEntity) {
final MapRoleEntity res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapRoleEntity::isUpdated);
private MapRoleEntity<K> registerEntityForChanges(MapRoleEntity<K> origEntity) {
final MapRoleEntity<K> res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapRoleEntity<K>::isUpdated);
return res;
}
@ -88,11 +92,11 @@ public class MapRoleProvider implements RoleProvider {
throw new ModelDuplicateException("Role exists: " + id);
}
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? roleStore.getKeyConvertor().yieldNewUniqueKey() : roleStore.getKeyConvertor().fromString(id);
LOG.tracef("addRealmRole(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
MapRoleEntity entity = new MapRoleEntity(entityId, realm.getId());
MapRoleEntity<K> entity = new MapRoleEntity<K>(entityId, realm.getId());
entity.setName(name);
entity.setRealmId(realm.getId());
if (tx.read(entity.getId()) != null) {
@ -124,11 +128,11 @@ public class MapRoleProvider implements RoleProvider {
throw new ModelDuplicateException("Role exists: " + id);
}
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? roleStore.getKeyConvertor().yieldNewUniqueKey() : roleStore.getKeyConvertor().fromString(id);
LOG.tracef("addClientRole(%s, %s, %s)%s", client, id, name, getShortStackTrace());
MapRoleEntity entity = new MapRoleEntity(entityId, client.getRealm().getId());
MapRoleEntity<K> entity = new MapRoleEntity<K>(entityId, client.getRealm().getId());
entity.setName(name);
entity.setClientRole(true);
entity.setClientId(client.getId());
@ -168,13 +172,13 @@ public class MapRoleProvider implements RoleProvider {
.compare(SearchableFields.IS_COMPOSITE_ROLE, Operator.EQ, false);
//remove role from realm-roles composites
try (Stream<MapRoleEntity> baseStream = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapRoleEntity<K>> baseStream = tx.getUpdatedNotRemoved(mcb)) {
StreamUtils.leftInnerJoinIterable(baseStream, MapRoleEntity::getCompositeRoles)
StreamUtils.leftInnerJoinIterable(baseStream, MapRoleEntity<K>::getCompositeRoles)
.filter(pair -> role.getId().equals(pair.getV()))
.collect(Collectors.toSet())
.forEach(pair -> {
MapRoleEntity origEntity = pair.getK();
MapRoleEntity<K> origEntity = pair.getK();
//
// TODO: Investigate what this is for - the return value is ignored
@ -192,13 +196,13 @@ public class MapRoleProvider implements RoleProvider {
.compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId())
.compare(SearchableFields.IS_COMPOSITE_ROLE, Operator.EQ, false);
try (Stream<MapRoleEntity> baseStream = tx.getUpdatedNotRemoved(mcbClient)) {
try (Stream<MapRoleEntity<K>> baseStream = tx.getUpdatedNotRemoved(mcbClient)) {
StreamUtils.leftInnerJoinIterable(baseStream, MapRoleEntity::getCompositeRoles)
StreamUtils.leftInnerJoinIterable(baseStream, MapRoleEntity<K>::getCompositeRoles)
.filter(pair -> role.getId().equals(pair.getV()))
.collect(Collectors.toSet())
.forEach(pair -> {
MapRoleEntity origEntity = pair.getK();
MapRoleEntity<K> origEntity = pair.getK();
//
// TODO: Investigate what this is for - the return value is ignored
@ -223,7 +227,7 @@ public class MapRoleProvider implements RoleProvider {
});
// TODO: ^^^^^^^ Up to here
tx.delete(UUID.fromString(role.getId()));
tx.delete(roleStore.getKeyConvertor().fromString(role.getId()));
return true;
}
@ -287,7 +291,7 @@ public class MapRoleProvider implements RoleProvider {
LOG.tracef("getRoleById(%s, %s)%s", realm, id, getShortStackTrace());
MapRoleEntity entity = tx.read(UUID.fromString(id));
MapRoleEntity<K> entity = tx.read(roleStore.getKeyConvertor().fromStringSafe(id));
String realmId = realm.getId();
return (entity == null || ! Objects.equals(realmId, entity.getRealmId()))
? null
@ -306,7 +310,7 @@ public class MapRoleProvider implements RoleProvider {
roleStore.createCriteriaBuilder().compare(SearchableFields.DESCRIPTION, Operator.ILIKE, "%" + search + "%")
);
Stream<MapRoleEntity> s = tx.getUpdatedNotRemoved(mcb)
Stream<MapRoleEntity<K>> s = tx.getUpdatedNotRemoved(mcb)
.sorted(COMPARE_BY_NAME);
return paginatedStream(s.map(entityToAdapterFunc(realm)), first, max);
@ -324,7 +328,7 @@ public class MapRoleProvider implements RoleProvider {
roleStore.createCriteriaBuilder().compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"),
roleStore.createCriteriaBuilder().compare(SearchableFields.DESCRIPTION, Operator.ILIKE, "%" + search + "%")
);
Stream<MapRoleEntity> s = tx.getUpdatedNotRemoved(mcb)
Stream<MapRoleEntity<K>> s = tx.getUpdatedNotRemoved(mcb)
.sorted(COMPARE_BY_NAME);
return paginatedStream(s,first, max).map(entityToAdapterFunc(client.getRealm()));

View file

@ -16,30 +16,25 @@
*/
package org.keycloak.models.map.role;
import java.util.UUID;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RoleModel;
import org.keycloak.models.RoleProvider;
import org.keycloak.models.RoleProviderFactory;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
public class MapRoleProviderFactory extends AbstractMapProviderFactory<RoleProvider> implements RoleProviderFactory {
public class MapRoleProviderFactory<K> extends AbstractMapProviderFactory<RoleProvider, K, MapRoleEntity<K>, RoleModel> implements RoleProviderFactory {
private MapStorage<UUID, MapRoleEntity, RoleModel> store;
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("roles", UUID.class, MapRoleEntity.class, RoleModel.class);
public MapRoleProviderFactory() {
super(MapRoleEntity.class, RoleModel.class);
}
@Override
public RoleProvider create(KeycloakSession session) {
return new MapRoleProvider(session, store);
return new MapRoleProvider<>(session, getStorage(session));
}
@Override
public String getHelpText() {
return "Role provider";
}
}

View file

@ -27,14 +27,21 @@ import org.keycloak.common.util.RandomString;
import org.keycloak.migration.MigrationModel;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ServerInfoProvider;
import org.keycloak.models.ServerInfoProviderFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
public class MapServerInfoProviderFactory extends AbstractMapProviderFactory<ServerInfoProvider> implements ServerInfoProviderFactory {
public class MapServerInfoProviderFactory implements ServerInfoProviderFactory {
public static final String PROVIDER_ID = "map";
private static final String RESOURCES_VERSION_SEED = "resourcesVersionSeed";
@Override
public ServerInfoProvider create(KeycloakSession session) {
return INSTANCE;
}
@Override
public void init(Config.Scope config) {
String seed = config.get(RESOURCES_VERSION_SEED);
@ -53,8 +60,16 @@ public class MapServerInfoProviderFactory extends AbstractMapProviderFactory<Ser
}
@Override
public ServerInfoProvider create(KeycloakSession session) {
return INSTANCE;
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void close() {
}
private static final ServerInfoProvider INSTANCE = new ServerInfoProvider() {

View file

@ -30,28 +30,28 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.authSession.AbstractRootAuthenticationSessionEntity;
import org.keycloak.models.map.authorization.entity.AbstractPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.AbstractPolicyEntity;
import org.keycloak.models.map.authorization.entity.AbstractResourceEntity;
import org.keycloak.models.map.authorization.entity.AbstractResourceServerEntity;
import org.keycloak.models.map.authorization.entity.AbstractScopeEntity;
import org.keycloak.models.map.client.AbstractClientEntity;
import org.keycloak.models.map.clientscope.AbstractClientScopeEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.group.AbstractGroupEntity;
import org.keycloak.models.map.realm.AbstractRealmEntity;
import org.keycloak.models.map.role.AbstractRoleEntity;
import org.keycloak.models.map.userSession.AbstractAuthenticatedClientSessionEntity;
import org.keycloak.models.map.loginFailure.AbstractUserLoginFailureEntity;
import org.keycloak.models.map.userSession.AbstractUserSessionEntity;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.storage.SearchableModelField;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.models.map.storage.MapModelCriteriaBuilder.UpdatePredicatesFunc;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.user.AbstractUserEntity;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.user.UserConsentEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.storage.StorageId;
import java.util.Arrays;
@ -69,57 +69,57 @@ import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
*/
public class MapFieldPredicates {
public static final Map<SearchableModelField<RealmModel>, UpdatePredicatesFunc<Object, AbstractRealmEntity<Object>, RealmModel>> REALM_PREDICATES = basePredicates(RealmModel.SearchableFields.ID);
public static final Map<SearchableModelField<ClientModel>, UpdatePredicatesFunc<Object, AbstractClientEntity<Object>, ClientModel>> CLIENT_PREDICATES = basePredicates(ClientModel.SearchableFields.ID);
public static final Map<SearchableModelField<ClientScopeModel>, UpdatePredicatesFunc<Object, AbstractClientScopeEntity<Object>, ClientScopeModel>> CLIENT_SCOPE_PREDICATES = basePredicates(ClientScopeModel.SearchableFields.ID);
public static final Map<SearchableModelField<GroupModel>, UpdatePredicatesFunc<Object, AbstractGroupEntity<Object>, GroupModel>> GROUP_PREDICATES = basePredicates(GroupModel.SearchableFields.ID);
public static final Map<SearchableModelField<RoleModel>, UpdatePredicatesFunc<Object, AbstractRoleEntity<Object>, RoleModel>> ROLE_PREDICATES = basePredicates(RoleModel.SearchableFields.ID);
public static final Map<SearchableModelField<UserModel>, UpdatePredicatesFunc<Object, AbstractUserEntity<Object>, UserModel>> USER_PREDICATES = basePredicates(UserModel.SearchableFields.ID);
public static final Map<SearchableModelField<RootAuthenticationSessionModel>, UpdatePredicatesFunc<Object, AbstractRootAuthenticationSessionEntity<Object>, RootAuthenticationSessionModel>> AUTHENTICATION_SESSION_PREDICATES = basePredicates(RootAuthenticationSessionModel.SearchableFields.ID);
public static final Map<SearchableModelField<ResourceServer>, UpdatePredicatesFunc<Object, AbstractResourceServerEntity<Object>, ResourceServer>> AUTHZ_RESOURCE_SERVER_PREDICATES = basePredicates(ResourceServer.SearchableFields.ID);
public static final Map<SearchableModelField<Resource>, UpdatePredicatesFunc<Object, AbstractResourceEntity<Object>, Resource>> AUTHZ_RESOURCE_PREDICATES = basePredicates(Resource.SearchableFields.ID);
public static final Map<SearchableModelField<Scope>, UpdatePredicatesFunc<Object, AbstractScopeEntity<Object>, Scope>> AUTHZ_SCOPE_PREDICATES = basePredicates(Scope.SearchableFields.ID);
public static final Map<SearchableModelField<PermissionTicket>, UpdatePredicatesFunc<Object, AbstractPermissionTicketEntity<Object>, PermissionTicket>> AUTHZ_PERMISSION_TICKET_PREDICATES = basePredicates(PermissionTicket.SearchableFields.ID);
public static final Map<SearchableModelField<Policy>, UpdatePredicatesFunc<Object, AbstractPolicyEntity<Object>, Policy>> AUTHZ_POLICY_PREDICATES = basePredicates(Policy.SearchableFields.ID);
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, AbstractUserSessionEntity<Object>, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
public static final Map<SearchableModelField<AuthenticatedClientSessionModel>, UpdatePredicatesFunc<Object, AbstractAuthenticatedClientSessionEntity<Object>, AuthenticatedClientSessionModel>> CLIENT_SESSION_PREDICATES = basePredicates(AuthenticatedClientSessionModel.SearchableFields.ID);
public static final Map<SearchableModelField<UserLoginFailureModel>, UpdatePredicatesFunc<Object, AbstractUserLoginFailureEntity<Object>, UserLoginFailureModel>> USER_LOGIN_FAILURE_PREDICATES = basePredicates(UserLoginFailureModel.SearchableFields.ID);
public static final Map<SearchableModelField<AuthenticatedClientSessionModel>, UpdatePredicatesFunc<Object, MapAuthenticatedClientSessionEntity<Object>, AuthenticatedClientSessionModel>> CLIENT_SESSION_PREDICATES = basePredicates(AuthenticatedClientSessionModel.SearchableFields.ID);
public static final Map<SearchableModelField<ClientModel>, UpdatePredicatesFunc<Object, MapClientEntity<Object>, ClientModel>> CLIENT_PREDICATES = basePredicates(ClientModel.SearchableFields.ID);
public static final Map<SearchableModelField<ClientScopeModel>, UpdatePredicatesFunc<Object, MapClientScopeEntity<Object>, ClientScopeModel>> CLIENT_SCOPE_PREDICATES = basePredicates(ClientScopeModel.SearchableFields.ID);
public static final Map<SearchableModelField<GroupModel>, UpdatePredicatesFunc<Object, MapGroupEntity<Object>, GroupModel>> GROUP_PREDICATES = basePredicates(GroupModel.SearchableFields.ID);
public static final Map<SearchableModelField<RoleModel>, UpdatePredicatesFunc<Object, MapRoleEntity<Object>, RoleModel>> ROLE_PREDICATES = basePredicates(RoleModel.SearchableFields.ID);
public static final Map<SearchableModelField<RootAuthenticationSessionModel>, UpdatePredicatesFunc<Object, MapRootAuthenticationSessionEntity<Object>, RootAuthenticationSessionModel>> AUTHENTICATION_SESSION_PREDICATES = basePredicates(RootAuthenticationSessionModel.SearchableFields.ID);
public static final Map<SearchableModelField<RealmModel>, UpdatePredicatesFunc<Object, MapRealmEntity<Object>, RealmModel>> REALM_PREDICATES = basePredicates(RealmModel.SearchableFields.ID);
public static final Map<SearchableModelField<ResourceServer>, UpdatePredicatesFunc<Object, MapResourceServerEntity<Object>, ResourceServer>> AUTHZ_RESOURCE_SERVER_PREDICATES = basePredicates(ResourceServer.SearchableFields.ID);
public static final Map<SearchableModelField<Resource>, UpdatePredicatesFunc<Object, MapResourceEntity<Object>, Resource>> AUTHZ_RESOURCE_PREDICATES = basePredicates(Resource.SearchableFields.ID);
public static final Map<SearchableModelField<Scope>, UpdatePredicatesFunc<Object, MapScopeEntity<Object>, Scope>> AUTHZ_SCOPE_PREDICATES = basePredicates(Scope.SearchableFields.ID);
public static final Map<SearchableModelField<PermissionTicket>, UpdatePredicatesFunc<Object, MapPermissionTicketEntity<Object>, PermissionTicket>> AUTHZ_PERMISSION_TICKET_PREDICATES = basePredicates(PermissionTicket.SearchableFields.ID);
public static final Map<SearchableModelField<Policy>, UpdatePredicatesFunc<Object, MapPolicyEntity<Object>, Policy>> AUTHZ_POLICY_PREDICATES = basePredicates(Policy.SearchableFields.ID);
public static final Map<SearchableModelField<UserLoginFailureModel>, UpdatePredicatesFunc<Object, MapUserLoginFailureEntity<Object>, UserLoginFailureModel>> USER_LOGIN_FAILURE_PREDICATES = basePredicates(UserLoginFailureModel.SearchableFields.ID);
public static final Map<SearchableModelField<UserModel>, UpdatePredicatesFunc<Object, MapUserEntity<Object>, UserModel>> USER_PREDICATES = basePredicates(UserModel.SearchableFields.ID);
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, MapUserSessionEntity<Object>, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
@SuppressWarnings("unchecked")
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
static {
put(REALM_PREDICATES, RealmModel.SearchableFields.NAME, AbstractRealmEntity::getName);
put(REALM_PREDICATES, RealmModel.SearchableFields.NAME, MapRealmEntity::getName);
put(REALM_PREDICATES, RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, MapFieldPredicates::checkRealmsWithClientInitialAccess);
put(REALM_PREDICATES, RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, MapFieldPredicates::checkRealmsWithComponentType);
put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, AbstractClientEntity::getRealmId);
put(CLIENT_PREDICATES, ClientModel.SearchableFields.CLIENT_ID, AbstractClientEntity::getClientId);
put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, MapClientEntity::getRealmId);
put(CLIENT_PREDICATES, ClientModel.SearchableFields.CLIENT_ID, MapClientEntity::getClientId);
put(CLIENT_PREDICATES, ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, MapFieldPredicates::checkScopeMappingRole);
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.REALM_ID, AbstractClientScopeEntity::getRealmId);
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.NAME, AbstractClientScopeEntity::getName);
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.REALM_ID, MapClientScopeEntity::getRealmId);
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.NAME, MapClientScopeEntity::getName);
put(GROUP_PREDICATES, GroupModel.SearchableFields.REALM_ID, AbstractGroupEntity::getRealmId);
put(GROUP_PREDICATES, GroupModel.SearchableFields.NAME, AbstractGroupEntity::getName);
put(GROUP_PREDICATES, GroupModel.SearchableFields.PARENT_ID, AbstractGroupEntity::getParentId);
put(GROUP_PREDICATES, GroupModel.SearchableFields.REALM_ID, MapGroupEntity::getRealmId);
put(GROUP_PREDICATES, GroupModel.SearchableFields.NAME, MapGroupEntity::getName);
put(GROUP_PREDICATES, GroupModel.SearchableFields.PARENT_ID, MapGroupEntity::getParentId);
put(GROUP_PREDICATES, GroupModel.SearchableFields.ASSIGNED_ROLE, MapFieldPredicates::checkGrantedGroupRole);
put(ROLE_PREDICATES, RoleModel.SearchableFields.REALM_ID, AbstractRoleEntity::getRealmId);
put(ROLE_PREDICATES, RoleModel.SearchableFields.CLIENT_ID, AbstractRoleEntity::getClientId);
put(ROLE_PREDICATES, RoleModel.SearchableFields.DESCRIPTION, AbstractRoleEntity::getDescription);
put(ROLE_PREDICATES, RoleModel.SearchableFields.NAME, AbstractRoleEntity::getName);
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_CLIENT_ROLE, AbstractRoleEntity::isClientRole);
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_COMPOSITE_ROLE, AbstractRoleEntity::isComposite);
put(ROLE_PREDICATES, RoleModel.SearchableFields.REALM_ID, MapRoleEntity::getRealmId);
put(ROLE_PREDICATES, RoleModel.SearchableFields.CLIENT_ID, MapRoleEntity::getClientId);
put(ROLE_PREDICATES, RoleModel.SearchableFields.DESCRIPTION, MapRoleEntity::getDescription);
put(ROLE_PREDICATES, RoleModel.SearchableFields.NAME, MapRoleEntity::getName);
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_CLIENT_ROLE, MapRoleEntity::isClientRole);
put(ROLE_PREDICATES, RoleModel.SearchableFields.IS_COMPOSITE_ROLE, MapRoleEntity::isComposite);
put(USER_PREDICATES, UserModel.SearchableFields.REALM_ID, AbstractUserEntity::getRealmId);
put(USER_PREDICATES, UserModel.SearchableFields.USERNAME, AbstractUserEntity::getUsername);
put(USER_PREDICATES, UserModel.SearchableFields.FIRST_NAME, AbstractUserEntity::getFirstName);
put(USER_PREDICATES, UserModel.SearchableFields.LAST_NAME, AbstractUserEntity::getLastName);
put(USER_PREDICATES, UserModel.SearchableFields.EMAIL, AbstractUserEntity::getEmail);
put(USER_PREDICATES, UserModel.SearchableFields.ENABLED, AbstractUserEntity::isEnabled);
put(USER_PREDICATES, UserModel.SearchableFields.EMAIL_VERIFIED, AbstractUserEntity::isEmailVerified);
put(USER_PREDICATES, UserModel.SearchableFields.FEDERATION_LINK, AbstractUserEntity::getFederationLink);
put(USER_PREDICATES, UserModel.SearchableFields.REALM_ID, MapUserEntity::getRealmId);
put(USER_PREDICATES, UserModel.SearchableFields.USERNAME, MapUserEntity::getUsername);
put(USER_PREDICATES, UserModel.SearchableFields.FIRST_NAME, MapUserEntity::getFirstName);
put(USER_PREDICATES, UserModel.SearchableFields.LAST_NAME, MapUserEntity::getLastName);
put(USER_PREDICATES, UserModel.SearchableFields.EMAIL, MapUserEntity::getEmail);
put(USER_PREDICATES, UserModel.SearchableFields.ENABLED, MapUserEntity::isEnabled);
put(USER_PREDICATES, UserModel.SearchableFields.EMAIL_VERIFIED, MapUserEntity::isEmailVerified);
put(USER_PREDICATES, UserModel.SearchableFields.FEDERATION_LINK, MapUserEntity::getFederationLink);
put(USER_PREDICATES, UserModel.SearchableFields.ATTRIBUTE, MapFieldPredicates::checkUserAttributes);
put(USER_PREDICATES, UserModel.SearchableFields.IDP_AND_USER, MapFieldPredicates::getUserIdpAliasAtIdentityProviderPredicate);
put(USER_PREDICATES, UserModel.SearchableFields.ASSIGNED_ROLE, MapFieldPredicates::checkGrantedUserRole);
@ -127,62 +127,62 @@ public class MapFieldPredicates {
put(USER_PREDICATES, UserModel.SearchableFields.CONSENT_FOR_CLIENT, MapFieldPredicates::checkUserClientConsent);
put(USER_PREDICATES, UserModel.SearchableFields.CONSENT_WITH_CLIENT_SCOPE, MapFieldPredicates::checkUserConsentsWithClientScope);
put(USER_PREDICATES, UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, MapFieldPredicates::getUserConsentClientFederationLink);
put(USER_PREDICATES, UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, AbstractUserEntity::getServiceAccountClientLink);
put(USER_PREDICATES, UserModel.SearchableFields.SERVICE_ACCOUNT_CLIENT, MapUserEntity::getServiceAccountClientLink);
put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.REALM_ID, AbstractRootAuthenticationSessionEntity::getRealmId);
put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.TIMESTAMP, AbstractRootAuthenticationSessionEntity::getTimestamp);
put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.REALM_ID, MapRootAuthenticationSessionEntity::getRealmId);
put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.TIMESTAMP, MapRootAuthenticationSessionEntity::getTimestamp);
put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.ID, AbstractResourceServerEntity::getId);
put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.ID, MapResourceServerEntity::getId);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.ID, predicateForKeyField(AbstractResourceEntity::getId));
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.NAME, AbstractResourceEntity::getName);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.RESOURCE_SERVER_ID, AbstractResourceEntity::getResourceServerId);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.OWNER, AbstractResourceEntity::getOwner);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.TYPE, AbstractResourceEntity::getType);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.ID, predicateForKeyField(MapResourceEntity::getId));
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.NAME, MapResourceEntity::getName);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.RESOURCE_SERVER_ID, MapResourceEntity::getResourceServerId);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.OWNER, MapResourceEntity::getOwner);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.TYPE, MapResourceEntity::getType);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.URI, MapFieldPredicates::checkResourceUri);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.SCOPE_ID, MapFieldPredicates::checkResourceScopes);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.OWNER_MANAGED_ACCESS, AbstractResourceEntity::isOwnerManagedAccess);
put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.OWNER_MANAGED_ACCESS, MapResourceEntity::isOwnerManagedAccess);
put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.ID, predicateForKeyField(AbstractScopeEntity::getId));
put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.RESOURCE_SERVER_ID, AbstractScopeEntity::getResourceServerId);
put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.NAME, AbstractScopeEntity::getName);
put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.ID, predicateForKeyField(MapScopeEntity::getId));
put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.RESOURCE_SERVER_ID, MapScopeEntity::getResourceServerId);
put(AUTHZ_SCOPE_PREDICATES, Scope.SearchableFields.NAME, MapScopeEntity::getName);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.ID, predicateForKeyField(AbstractPermissionTicketEntity::getId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.OWNER, AbstractPermissionTicketEntity::getOwner);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.REQUESTER, AbstractPermissionTicketEntity::getRequester);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.RESOURCE_SERVER_ID, AbstractPermissionTicketEntity::getResourceServerId);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.RESOURCE_ID, predicateForKeyField(AbstractPermissionTicketEntity::getResourceId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.SCOPE_ID, predicateForKeyField(AbstractPermissionTicketEntity::getScopeId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.POLICY_ID, predicateForKeyField(AbstractPermissionTicketEntity::getPolicyId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.GRANTED_TIMESTAMP, AbstractPermissionTicketEntity::getGrantedTimestamp);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.ID, predicateForKeyField(MapPermissionTicketEntity::getId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.OWNER, MapPermissionTicketEntity::getOwner);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.REQUESTER, MapPermissionTicketEntity::getRequester);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.RESOURCE_SERVER_ID, MapPermissionTicketEntity::getResourceServerId);
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.RESOURCE_ID, predicateForKeyField(MapPermissionTicketEntity::getResourceId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.SCOPE_ID, predicateForKeyField(MapPermissionTicketEntity::getScopeId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.POLICY_ID, predicateForKeyField(MapPermissionTicketEntity::getPolicyId));
put(AUTHZ_PERMISSION_TICKET_PREDICATES, PermissionTicket.SearchableFields.GRANTED_TIMESTAMP, MapPermissionTicketEntity::getGrantedTimestamp);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ID, predicateForKeyField(AbstractPolicyEntity::getId));
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.NAME, AbstractPolicyEntity::getName);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.OWNER, AbstractPolicyEntity::getOwner);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.TYPE, AbstractPolicyEntity::getType);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.RESOURCE_SERVER_ID, AbstractPolicyEntity::getResourceServerId);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ID, predicateForKeyField(MapPolicyEntity::getId));
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.NAME, MapPolicyEntity::getName);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.OWNER, MapPolicyEntity::getOwner);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.TYPE, MapPolicyEntity::getType);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.RESOURCE_SERVER_ID, MapPolicyEntity::getResourceServerId);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.RESOURCE_ID, MapFieldPredicates::checkPolicyResources);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.SCOPE_ID, MapFieldPredicates::checkPolicyScopes);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.CONFIG, MapFieldPredicates::checkPolicyConfig);
put(AUTHZ_POLICY_PREDICATES, Policy.SearchableFields.ASSOCIATED_POLICY_ID, MapFieldPredicates::checkAssociatedPolicy);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, use -> use.getNote(CORRESPONDING_SESSION_ID));
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.REALM_ID, AbstractUserSessionEntity::getRealmId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.USER_ID, AbstractUserSessionEntity::getUserId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.REALM_ID, MapUserSessionEntity::getRealmId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.USER_ID, MapUserSessionEntity::getUserId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.CLIENT_ID, MapFieldPredicates::checkUserSessionContainsAuthenticatedClientSession);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_SESSION_ID, AbstractUserSessionEntity::getBrokerSessionId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_USER_ID, AbstractUserSessionEntity::getBrokerUserId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.IS_OFFLINE, AbstractUserSessionEntity::isOffline);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.LAST_SESSION_REFRESH, AbstractUserSessionEntity::getLastSessionRefresh);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_SESSION_ID, MapUserSessionEntity::getBrokerSessionId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.BROKER_USER_ID, MapUserSessionEntity::getBrokerUserId);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.IS_OFFLINE, MapUserSessionEntity::isOffline);
put(USER_SESSION_PREDICATES, UserSessionModel.SearchableFields.LAST_SESSION_REFRESH, MapUserSessionEntity::getLastSessionRefresh);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.REALM_ID, AbstractAuthenticatedClientSessionEntity::getRealmId);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.CLIENT_ID, AbstractAuthenticatedClientSessionEntity::getClientId);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, AbstractAuthenticatedClientSessionEntity::getUserSessionId);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.IS_OFFLINE, AbstractAuthenticatedClientSessionEntity::isOffline);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.TIMESTAMP, AbstractAuthenticatedClientSessionEntity::getTimestamp);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.REALM_ID, MapAuthenticatedClientSessionEntity::getRealmId);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.CLIENT_ID, MapAuthenticatedClientSessionEntity::getClientId);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, MapAuthenticatedClientSessionEntity::getUserSessionId);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.IS_OFFLINE, MapAuthenticatedClientSessionEntity::isOffline);
put(CLIENT_SESSION_PREDICATES, AuthenticatedClientSessionModel.SearchableFields.TIMESTAMP, MapAuthenticatedClientSessionEntity::getTimestamp);
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.REALM_ID, AbstractUserLoginFailureEntity::getRealmId);
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.USER_ID, AbstractUserLoginFailureEntity::getUserId);
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.REALM_ID, MapUserLoginFailureEntity::getRealmId);
put(USER_LOGIN_FAILURE_PREDICATES, UserLoginFailureModel.SearchableFields.USER_ID, MapUserLoginFailureEntity::getUserId);
}
static {
@ -242,30 +242,30 @@ public class MapFieldPredicates {
return expectedType.cast(ob);
}
private static MapModelCriteriaBuilder<Object, AbstractClientEntity<Object>, ClientModel> checkScopeMappingRole(MapModelCriteriaBuilder<Object, AbstractClientEntity<Object>, ClientModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapClientEntity<Object>, ClientModel> checkScopeMappingRole(MapModelCriteriaBuilder<Object, MapClientEntity<Object>, ClientModel> mcb, Operator op, Object[] values) {
String roleIdS = ensureEqSingleValue(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "role_id", op, values);
Function<AbstractClientEntity<Object>, ?> getter;
Function<MapClientEntity<Object>, ?> getter;
getter = ce -> ce.getScopeMappings().contains(roleIdS);
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractGroupEntity<Object>, GroupModel> checkGrantedGroupRole(MapModelCriteriaBuilder<Object, AbstractGroupEntity<Object>, GroupModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapGroupEntity<Object>, GroupModel> checkGrantedGroupRole(MapModelCriteriaBuilder<Object, MapGroupEntity<Object>, GroupModel> mcb, Operator op, Object[] values) {
String roleIdS = ensureEqSingleValue(GroupModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values);
Function<AbstractGroupEntity<Object>, ?> getter;
Function<MapGroupEntity<Object>, ?> getter;
getter = ge -> ge.getGrantedRoles().contains(roleIdS);
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> getUserConsentClientFederationLink(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> getUserConsentClientFederationLink(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
String providerId = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, "provider_id", op, values);
String providerIdS = new StorageId((String) providerId, "").getId();
Function<AbstractUserEntity<Object>, ?> getter;
Function<MapUserEntity<Object>, ?> getter;
getter = ue -> ue.getUserConsents().map(UserConsentEntity::getClientId).anyMatch(v -> v != null && v.startsWith(providerIdS));
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> checkUserAttributes(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> checkUserAttributes(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
if (values == null || values.length <= 1) {
throw new CriterionNotSupportedException(UserModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (attribute_name, ...), got: " + Arrays.toString(values));
}
@ -275,7 +275,7 @@ public class MapFieldPredicates {
throw new CriterionNotSupportedException(UserModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values));
}
String attrNameS = (String) attrName;
Function<AbstractUserEntity<Object>, ?> getter;
Function<MapUserEntity<Object>, ?> getter;
Object[] realValues = new Object[values.length - 1];
System.arraycopy(values, 1, realValues, 0, values.length - 1);
Predicate<Object> valueComparator = CriteriaOperator.predicateFor(op, realValues);
@ -287,16 +287,16 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> checkGrantedUserRole(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> checkGrantedUserRole(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
String roleIdS = ensureEqSingleValue(UserModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values);
Function<AbstractUserEntity<Object>, ?> getter;
Function<MapUserEntity<Object>, ?> getter;
getter = ue -> ue.getRolesMembership().contains(roleIdS);
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractResourceEntity<Object>, Resource> checkResourceUri(MapModelCriteriaBuilder<Object, AbstractResourceEntity<Object>, Resource> mcb, Operator op, Object[] values) {
Function<AbstractResourceEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapResourceEntity<Object>, Resource> checkResourceUri(MapModelCriteriaBuilder<Object, MapResourceEntity<Object>, Resource> mcb, Operator op, Object[] values) {
Function<MapResourceEntity<Object>, ?> getter;
if (Operator.EXISTS.equals(op)) {
getter = re -> re.getUris() != null && !re.getUris().isEmpty();
@ -311,8 +311,8 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractResourceEntity<Object>, Resource> checkResourceScopes(MapModelCriteriaBuilder<Object, AbstractResourceEntity<Object>, Resource> mcb, Operator op, Object[] values) {
Function<AbstractResourceEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapResourceEntity<Object>, Resource> checkResourceScopes(MapModelCriteriaBuilder<Object, MapResourceEntity<Object>, Resource> mcb, Operator op, Object[] values) {
Function<MapResourceEntity<Object>, ?> getter;
if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) {
Collection<?> c = (Collection<?>) values[0];
@ -325,8 +325,8 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> checkPolicyResources(MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<AbstractPolicyEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> checkPolicyResources(MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<MapPolicyEntity<Object>, ?> getter;
if (op == Operator.NOT_EXISTS) {
getter = re -> re.getResourceIds().isEmpty();
@ -341,8 +341,8 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> checkPolicyScopes(MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<AbstractPolicyEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> checkPolicyScopes(MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<MapPolicyEntity<Object>, ?> getter;
if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) {
Collection<?> c = (Collection<?>) values[0];
@ -355,8 +355,8 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> checkPolicyConfig(MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<AbstractPolicyEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> checkPolicyConfig(MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<MapPolicyEntity<Object>, ?> getter;
final Object attrName = values[0];
if (!(attrName instanceof String)) {
@ -375,8 +375,8 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> checkAssociatedPolicy(MapModelCriteriaBuilder<Object, AbstractPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<AbstractPolicyEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> checkAssociatedPolicy(MapModelCriteriaBuilder<Object, MapPolicyEntity<Object>, Policy> mcb, Operator op, Object[] values) {
Function<MapPolicyEntity<Object>, ?> getter;
if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) {
Collection<?> c = (Collection<?>) values[0];
@ -389,8 +389,8 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> checkUserGroup(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
Function<AbstractUserEntity<Object>, ?> getter;
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> checkUserGroup(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
Function<MapUserEntity<Object>, ?> getter;
if (op == Operator.IN && values != null && values.length == 1 && (values[0] instanceof Collection)) {
Collection<?> c = (Collection<?>) values[0];
getter = ue -> ue.getGroupsMembership().stream().anyMatch(c::contains);
@ -402,23 +402,23 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> checkUserClientConsent(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> checkUserClientConsent(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
String clientIdS = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "client_id", op, values);
Function<AbstractUserEntity<Object>, ?> getter;
Function<MapUserEntity<Object>, ?> getter;
getter = ue -> ue.getUserConsent(clientIdS);
return mcb.fieldCompare(Operator.EXISTS, getter, null);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> checkUserConsentsWithClientScope(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> checkUserConsentsWithClientScope(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
String clientScopeIdS = ensureEqSingleValue(UserModel.SearchableFields.CONSENT_FOR_CLIENT, "client_scope_id", op, values);
Function<AbstractUserEntity<Object>, ?> getter;
Function<MapUserEntity<Object>, ?> getter;
getter = ue -> ue.getUserConsents().anyMatch(consent -> consent.getGrantedClientScopesIds().contains(clientScopeIdS));
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> getUserIdpAliasAtIdentityProviderPredicate(MapModelCriteriaBuilder<Object, AbstractUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> getUserIdpAliasAtIdentityProviderPredicate(MapModelCriteriaBuilder<Object, MapUserEntity<Object>, UserModel> mcb, Operator op, Object[] values) {
if (op != Operator.EQ) {
throw new CriterionNotSupportedException(UserModel.SearchableFields.IDP_AND_USER, op);
}
@ -427,7 +427,7 @@ public class MapFieldPredicates {
}
final Object idpAlias = values[0];
Function<AbstractUserEntity<Object>, ?> getter;
Function<MapUserEntity<Object>, ?> getter;
if (values.length == 1) {
getter = ue -> ue.getFederatedIdentities()
.anyMatch(aue -> Objects.equals(idpAlias, aue.getIdentityProvider()));
@ -444,25 +444,23 @@ public class MapFieldPredicates {
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> checkRealmsWithClientInitialAccess(MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapRealmEntity<Object>, RealmModel> checkRealmsWithClientInitialAccess(MapModelCriteriaBuilder<Object, MapRealmEntity<Object>, RealmModel> mcb, Operator op, Object[] values) {
if (op != Operator.EXISTS) {
throw new CriterionNotSupportedException(RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, op);
}
Function<AbstractRealmEntity<Object>, ?> getter = AbstractRealmEntity::hasClientInitialAccess;
Function<MapRealmEntity<Object>, ?> getter = MapRealmEntity::hasClientInitialAccess;
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> checkRealmsWithComponentType(MapModelCriteriaBuilder<Object, AbstractRealmEntity<Object>, RealmModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapRealmEntity<Object>, RealmModel> checkRealmsWithComponentType(MapModelCriteriaBuilder<Object, MapRealmEntity<Object>, RealmModel> mcb, Operator op, Object[] values) {
String providerType = ensureEqSingleValue(RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, "component_provider_type", op, values);
Function<AbstractRealmEntity<Object>, ?> getter = realmEntity -> realmEntity.getComponents().anyMatch(component -> component.getProviderType().equals(providerType));
Function<MapRealmEntity<Object>, ?> getter = realmEntity -> realmEntity.getComponents().anyMatch(component -> component.getProviderType().equals(providerType));
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}
private static MapModelCriteriaBuilder<Object, AbstractUserSessionEntity<Object>, UserSessionModel> checkUserSessionContainsAuthenticatedClientSession(MapModelCriteriaBuilder<Object, AbstractUserSessionEntity<Object>, UserSessionModel> mcb, Operator op, Object[] values) {
private static MapModelCriteriaBuilder<Object, MapUserSessionEntity<Object>, UserSessionModel> checkUserSessionContainsAuthenticatedClientSession(MapModelCriteriaBuilder<Object, MapUserSessionEntity<Object>, UserSessionModel> mcb, Operator op, Object[] values) {
String clientId = ensureEqSingleValue(UserSessionModel.SearchableFields.CLIENT_ID, "client_id", op, values);
Function<AbstractUserSessionEntity<Object>, ?> getter;
getter = use -> (use.getAuthenticatedClientSessions().containsKey(clientId));
Function<MapUserSessionEntity<Object>, ?> getter = use -> (use.getAuthenticatedClientSessions().containsKey(clientId));
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
}

View file

@ -54,7 +54,7 @@ public class MapKeycloakTransaction<K, V extends AbstractEntity<K>, M> implement
@Override
public void commit() {
log.trace("Commit");
log.tracef("Commit - %s", map);
if (rollback) {
throw new RuntimeException("Rollback only!");
@ -97,7 +97,11 @@ public class MapKeycloakTransaction<K, V extends AbstractEntity<K>, M> implement
// This is for possibility to lookup for session by id, which was created in this transaction
public V read(K key) {
try { // TODO: Consider using Optional rather than handling NPE
return read(key, map::read);
} catch (NullPointerException ex) {
return null;
}
}
public V read(K key, Function<K, V> defaultValueFunc) {
@ -127,8 +131,6 @@ public class MapKeycloakTransaction<K, V extends AbstractEntity<K>, M> implement
* Returns the stream of records that match given criteria and includes changes made in this transaction, i.e.
* the result contains updates and excludes records that have been deleted in this transaction.
*
* Note that returned stream might not reflect on the bulk delete. This is known limitation that can be fixed if necessary.
*
* @param mcb
* @return
*/

View file

@ -49,6 +49,8 @@ public interface MapStorage<K, V extends AbstractEntity<K>, M> {
/**
* Returns object with the given {@code key} from the storage or {@code null} if object does not exist.
* <br>
* TODO: Consider returning {@code Optional<V>} instead.
* @param key Key of the object. Must not be {@code null}.
* @return See description
* @throws NullPointerException if the {@code key} is {@code null}
@ -115,7 +117,7 @@ public interface MapStorage<K, V extends AbstractEntity<K>, M> {
* If possible, do <i>not</i> delay filtering after the models are reconstructed from
* storage entities, in most cases this would be highly inefficient.
*
* @return See description
* @return See description. Never returns {@code null}
*/
ModelCriteriaBuilder<M> createCriteriaBuilder();
@ -126,8 +128,16 @@ public interface MapStorage<K, V extends AbstractEntity<K>, M> {
* shared same across storages accessing the same database within the same session; in other cases
* (e.g. plain map) a separate transaction handler might be created per each storage.
*
* @return See description.
* @return See description. Never returns {@code null}
*/
public MapKeycloakTransaction<K, V, M> createTransaction(KeycloakSession session);
/**
* Returns a {@link StringKeyConvertor} that is used to convert primary keys
* from {@link String} to internal representation and vice versa.
*
* @return See above. Never returns {@code null}.
*/
public StringKeyConvertor<K> getKeyConvertor();
}

View file

@ -27,12 +27,13 @@ import org.keycloak.provider.Provider;
public interface MapStorageProvider extends Provider {
/**
* Returns a key-value storage
* Returns a key-value storage implementation for the particular types.
* @param <K> type of the primary key
* @param <V> type of the value
* @param name Name of the storage
* @param flags
* @return
* @throws IllegalArgumentException If some of the types is not supported by the underlying implementation.
*/
<K, V extends AbstractEntity<K>, M> MapStorage<K, V, M> getStorage(String name, Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags);
<K, V extends AbstractEntity<K>, M> MapStorage<K, V, M> getStorage(Class<V> valueType, Class<M> modelType, Flag... flags);
}

View file

@ -16,27 +16,17 @@
*/
package org.keycloak.models.map.storage;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.component.ComponentFactory;
import org.keycloak.provider.ProviderFactory;
/**
*
* @author hmlnarik
*/
public interface MapStorageProviderFactory extends ProviderFactory<MapStorageProvider> {
public interface MapStorageProviderFactory extends ProviderFactory<MapStorageProvider>, ComponentFactory<MapStorageProvider, MapStorageProvider> {
public enum Flag {
INITIALIZE_EMPTY,
LOCAL
}
/**
* Returns a key-value storage
* @param <K> type of the primary key
* @param <V> type of the value
* @param name Name of the storage
* @param flags
* @return
*/
<K, V extends AbstractEntity<K>, M> MapStorage<K, V, M> getStorage(String name, Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags);
}

View file

@ -142,7 +142,7 @@ public interface ModelCriteriaBuilder<M> {
* Creates and returns a new instance of {@code ModelCriteriaBuilder} that
* combines the given builders with the Boolean OR operator.
* <p>
* Predicate coming out of {@code and} on an empty array of {@code builders}
* Predicate coming out of {@code or} on an empty array of {@code builders}
* (i.e. empty disjunction) is always {@code false}.
*
* <pre>

View file

@ -0,0 +1,126 @@
/*
* Copyright 2021 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.storage;
import java.security.SecureRandom;
import java.util.UUID;
/**
* Converts given storage key from and to {@code String} representation.
*
* @author hmlnarik
*/
public interface StringKeyConvertor<K> {
/**
* Returns String representation of the key from native representation
* @param key
* @throws IllegalArgumentException if the string format is not recognized
* @throws NullPointerException if the parameter is {@code null}
* @return See above
*/
default String keyToString(K key) { return key == null ? null : key.toString(); }
/**
* Returns a new unique primary key for the storage that
* this {@code StringKeyConvertor} belongs to. The uniqueness
* needs to be guaranteed by e.g. using database sequences or
* using a random value that is proved sufficiently improbable
* to be repeated.
*
* @return
*/
K yieldNewUniqueKey();
/**
* Returns native representation of the key from String representation
* @param key
* @throws IllegalArgumentException if the string format is not recognized
* @throws NullPointerException if the parameter is {@code null}
* @return See above
*/
K fromString(String key);
/**
* Exception-free variant of {@link #fromString} method.
* Returns native representation of the key from String representation,
* or {@code null} if the {@code key} is either {@code null} or invalid.
* @param key
* @return See above
*/
default K fromStringSafe(String key) {
try {
return fromString(key);
} catch (Exception ex) {
return null;
}
}
public static class UUIDKey implements StringKeyConvertor<UUID> {
public static final UUIDKey INSTANCE = new UUIDKey();
@Override
public UUID yieldNewUniqueKey() {
return UUID.randomUUID();
}
@Override
public UUID fromString(String key) {
return UUID.fromString(key);
}
}
public static class StringKey implements StringKeyConvertor<String> {
public static final StringKey INSTANCE = new StringKey();
@Override
public String fromString(String key) {
return key;
}
@Override
public String yieldNewUniqueKey() {
return fromString(UUID.randomUUID().toString());
}
}
public static class ULongKey implements StringKeyConvertor<Long> {
public static final ULongKey INSTANCE = new ULongKey();
/*
* The random number generator used by this class to create random
* based UUIDs. In a holder class to defer initialization until needed.
*/
private static class Holder {
static final SecureRandom numberGenerator = new SecureRandom();
}
@Override
public Long fromString(String key) {
return key == null ? null : Long.parseUnsignedLong(key);
}
@Override
public Long yieldNewUniqueKey() {
return Holder.numberGenerator.nextLong();
}
}
}

View file

@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Stream;
import org.keycloak.models.map.storage.MapModelCriteriaBuilder.UpdatePredicatesFunc;
import org.keycloak.models.map.storage.StringKeyConvertor;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Predicate;
@ -43,10 +44,12 @@ public class ConcurrentHashMapStorage<K, V extends AbstractEntity<K>, M> impleme
private final ConcurrentMap<K, V> store = new ConcurrentHashMap<>();
private final Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates;
private final StringKeyConvertor<K> keyConvertor;
@SuppressWarnings("unchecked")
public ConcurrentHashMapStorage(Class<M> modelClass) {
public ConcurrentHashMapStorage(Class<M> modelClass, StringKeyConvertor<K> keyConvertor) {
this.fieldPredicates = MapFieldPredicates.getPredicates(modelClass);
this.keyConvertor = keyConvertor;
}
@Override
@ -108,6 +111,10 @@ public class ConcurrentHashMapStorage<K, V extends AbstractEntity<K>, M> impleme
return sessionTransaction == null ? new MapKeycloakTransaction<>(this) : (MapKeycloakTransaction<K, V, M>) sessionTransaction;
}
@Override
public StringKeyConvertor<K> getKeyConvertor() {
return keyConvertor;
}
@Override
public Stream<V> read(ModelCriteriaBuilder<M> criteria) {

View file

@ -37,8 +37,8 @@ public class ConcurrentHashMapStorageProvider implements MapStorageProvider {
}
@Override
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(String name,
Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags) {
return factory.getStorage(name, keyType, valueType, modelType, flags);
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(
Class<V> valueType, Class<M> modelType, Flag... flags) {
return factory.getStorage(valueType, modelType, flags);
}
}

View file

@ -16,10 +16,23 @@
*/
package org.keycloak.models.map.storage.chm;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.Config.Scope;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.component.ComponentModelScope;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.Serialization;
@ -35,13 +48,18 @@ import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import java.util.UUID;
import org.keycloak.models.map.storage.StringKeyConvertor;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author hmlnarik
*/
public class ConcurrentHashMapStorageProviderFactory implements MapStorageProviderFactory {
public class ConcurrentHashMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>,MapStorageProviderFactory {
public static final String PROVIDER_ID = "concurrenthashmap";
@ -49,22 +67,69 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
private final ConcurrentHashMap<String, ConcurrentHashMapStorage<?,?,?>> storages = new ConcurrentHashMap<>();
private final Map<String, StringKeyConvertor> keyConvertors = new HashMap<>();
private File storageDirectory;
private String suffix;
private StringKeyConvertor defaultKeyConvertor;
public static final Map<Class<?>, String> MODEL_TO_NAME = new HashMap<>();
static {
MODEL_TO_NAME.put(AuthenticatedClientSessionModel.class, "client-sessions");
MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes");
MODEL_TO_NAME.put(ClientModel.class, "clients");
MODEL_TO_NAME.put(GroupModel.class, "groups");
MODEL_TO_NAME.put(RealmModel.class, "realms");
MODEL_TO_NAME.put(RoleModel.class, "roles");
MODEL_TO_NAME.put(RootAuthenticationSessionModel.class, "auth-sessions");
MODEL_TO_NAME.put(UserLoginFailureModel.class, "user-login-failures");
MODEL_TO_NAME.put(UserModel.class, "users");
MODEL_TO_NAME.put(UserSessionModel.class, "user-sessions");
// authz
MODEL_TO_NAME.put(PermissionTicket.class, "authz-permission-tickets");
MODEL_TO_NAME.put(Policy.class, "authz-policies");
MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers");
MODEL_TO_NAME.put(Resource.class, "authz-resources");
MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes");
}
private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>();
static {
KEY_CONVERTORS.put("uuid", StringKeyConvertor.UUIDKey.INSTANCE);
KEY_CONVERTORS.put("string", StringKeyConvertor.StringKey.INSTANCE);
KEY_CONVERTORS.put("ulong", StringKeyConvertor.ULongKey.INSTANCE);
}
@Override
public MapStorageProvider create(KeycloakSession session) {
return new ConcurrentHashMapStorageProvider(this);
}
@Override
public void init(Scope config) {
if (config instanceof ComponentModelScope) {
this.suffix = "-" + ((ComponentModelScope) config).getComponentId();
} else {
this.suffix = "";
}
final String keyType = config.get("keyType", "uuid");
defaultKeyConvertor = getKeyConvertor(keyType);
for (String name : MODEL_TO_NAME.values()) {
keyConvertors.put(name, getKeyConvertor(config.get("keyType." + name, keyType)));
}
final String dir = config.get("dir");
try {
if (dir == null || dir.trim().isEmpty()) {
LOG.warn("No directory set, created objects will not survive server restart");
this.storageDirectory = null;
} else {
File f = new File(dir);
try {
Files.createDirectories(f.toPath());
if (f.exists()) {
this.storageDirectory = f;
@ -72,11 +137,19 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null;
}
}
} catch (IOException ex) {
LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null;
}
}
private StringKeyConvertor getKeyConvertor(final String keyType) throws IllegalArgumentException {
StringKeyConvertor res = KEY_CONVERTORS.get(keyType);
if (res == null) {
throw new IllegalArgumentException("Unknown key type: " + keyType);
}
return res;
}
@Override
@ -88,17 +161,17 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
storages.forEach(this::storeMap);
}
private void storeMap(String fileName, ConcurrentHashMapStorage<?, ?, ?> store) {
if (fileName != null) {
File f = getFile(fileName);
private void storeMap(String mapName, ConcurrentHashMapStorage<?, ?, ?> store) {
if (mapName != null) {
File f = getFile(mapName);
try {
if (storageDirectory != null && storageDirectory.exists()) {
if (storageDirectory != null) {
LOG.debugf("Storing contents to %s", f.getCanonicalPath());
@SuppressWarnings("unchecked")
final ModelCriteriaBuilder readAllCriteria = store.createCriteriaBuilder();
Serialization.MAPPER.writeValue(f, store.read(readAllCriteria));
} else {
LOG.debugf("Not storing contents of %s because directory %s does not exist", fileName, this.storageDirectory);
LOG.debugf("Not storing contents of %s because directory not set", mapName);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
@ -106,19 +179,33 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
}
}
private <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> loadMap(String fileName,
private <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> loadMap(String mapName,
Class<V> valueType, Class<M> modelType, EnumSet<Flag> flags) {
final StringKeyConvertor kc = keyConvertors.getOrDefault(mapName, defaultKeyConvertor);
LOG.debugf("Initializing new map storage: %s", mapName);
@SuppressWarnings("unchecked")
ConcurrentHashMapStorage<K, V, M> store;
if (modelType == UserSessionModel.class) {
ConcurrentHashMapStorage clientSessionStore =
getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
store = new UserSessionConcurrentHashMapStorage<>(clientSessionStore);
ConcurrentHashMapStorage clientSessionStore = getStorage(MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
store = new UserSessionConcurrentHashMapStorage(clientSessionStore, kc) {
@Override
public String toString() {
return "ConcurrentHashMapStorage(" + mapName + suffix + ")";
}
};
} else {
store = new ConcurrentHashMapStorage<>(modelType);
store = new ConcurrentHashMapStorage(modelType, kc) {
@Override
public String toString() {
return "ConcurrentHashMapStorage(" + mapName + suffix + ")";
}
};
}
if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
final File f = getFile(fileName);
final File f = getFile(mapName);
if (f != null && f.exists()) {
try {
LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
@ -141,17 +228,38 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
}
@SuppressWarnings("unchecked")
@Override
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(String name,
Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags) {
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(
Class<V> valueType, Class<M> modelType, Flag... flags) {
EnumSet<Flag> f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags);
String name = MODEL_TO_NAME.getOrDefault(modelType, modelType.getSimpleName());
/* From ConcurrentHashMapStorage.computeIfAbsent javadoc:
*
* "... the computation [...] must not attempt to update any other mappings of this map."
*
* For UserSessionModel, there is a separate clientSessionStore in this CHM implementation. Thus
* we cannot guarantee that this won't be the case e.g. for user and client sessions. Hence we need
* to prepare clientSessionStore outside computeIfAbsent, otherwise deadlock occurs.
*/
if (modelType == UserSessionModel.class) {
getStorage(MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
}
return (ConcurrentHashMapStorage<K, V, M>) storages.computeIfAbsent(name, n -> loadMap(name, valueType, modelType, f));
}
private File getFile(String fileName) {
return storageDirectory == null
? null
: new File(storageDirectory, "map-" + fileName + ".json");
: new File(storageDirectory, "map-" + fileName + suffix + ".json");
}
@Override
public String getHelpText() {
return "In-memory ConcurrentHashMap storage";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
}

View file

@ -23,8 +23,9 @@ import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.userSession.AbstractAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.AbstractUserSessionEntity;
import org.keycloak.models.map.storage.StringKeyConvertor;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import java.util.Set;
import java.util.stream.Collectors;
@ -34,15 +35,15 @@ import java.util.stream.Collectors;
*
* @author hmlnarik
*/
public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapStorage<K, AbstractUserSessionEntity<K>, UserSessionModel> {
public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapStorage<K, MapUserSessionEntity<K>, UserSessionModel> {
private final ConcurrentHashMapStorage<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore;
private final ConcurrentHashMapStorage<K, MapAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore;
private class Transaction extends MapKeycloakTransaction<K, AbstractUserSessionEntity<K>, UserSessionModel> {
private class Transaction extends MapKeycloakTransaction<K, MapUserSessionEntity<K>, UserSessionModel> {
private final MapKeycloakTransaction<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionTr;
private final MapKeycloakTransaction<K, MapAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionTr;
public Transaction(MapKeycloakTransaction<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionTr) {
public Transaction(MapKeycloakTransaction<K, MapAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionTr) {
super(UserSessionConcurrentHashMapStorage.this);
this.clientSessionTr = clientSessionTr;
}
@ -65,15 +66,16 @@ public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapSto
}
@SuppressWarnings("unchecked")
public UserSessionConcurrentHashMapStorage(ConcurrentHashMapStorage<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore) {
super(UserSessionModel.class);
public UserSessionConcurrentHashMapStorage(ConcurrentHashMapStorage<K, MapAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore,
StringKeyConvertor<K> keyConvertor) {
super(UserSessionModel.class, keyConvertor);
this.clientSessionStore = clientSessionStore;
}
@Override
@SuppressWarnings("unchecked")
public MapKeycloakTransaction<K, AbstractUserSessionEntity<K>, UserSessionModel> createTransaction(KeycloakSession session) {
public MapKeycloakTransaction<K, MapUserSessionEntity<K>, UserSessionModel> createTransaction(KeycloakSession session) {
MapKeycloakTransaction sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
return sessionTransaction == null ? new Transaction(clientSessionStore.createTransaction(session)) : (MapKeycloakTransaction<K, AbstractUserSessionEntity<K>, UserSessionModel>) sessionTransaction;
return sessionTransaction == null ? new Transaction(clientSessionStore.createTransaction(session)) : (MapKeycloakTransaction<K, MapUserSessionEntity<K>, UserSessionModel>) sessionTransaction;
}
}

View file

@ -1,371 +0,0 @@
/*
* Copyright 2020 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.user;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* @author mhajas
*/
public abstract class AbstractUserEntity<K> implements AbstractEntity<K> {
private final K id;
private final String realmId;
private String username;
private String firstName;
private Long createdTimestamp;
private String lastName;
private String email;
private boolean enabled;
private boolean emailVerified;
// This is necessary to be able to dynamically switch unique email constraints on and off in the realm settings
private String emailConstraint = KeycloakModelUtils.generateId();
private Map<String, List<String>> attributes = new HashMap<>();
private Set<String> requiredActions = new HashSet<>();
private final Map<String, UserCredentialEntity> credentials = new HashMap<>();
private final List<String> credentialsOrder = new LinkedList<>();
private final Map<String, UserFederatedIdentityEntity> federatedIdentities = new HashMap<>();
private final Map<String, UserConsentEntity> userConsents = new HashMap<>();
private Set<String> groupsMembership = new HashSet<>();
private Set<String> rolesMembership = new HashSet<>();
private String federationLink;
private String serviceAccountClientLink;
private int notBefore;
static Comparator<AbstractUserEntity<?>> COMPARE_BY_USERNAME = Comparator.comparing(AbstractUserEntity::getUsername);
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected AbstractUserEntity() {
this.id = null;
this.realmId = null;
}
public AbstractUserEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated
|| userConsents.values().stream().anyMatch(UserConsentEntity::isUpdated)
|| credentials.values().stream().anyMatch(UserCredentialEntity::isUpdated)
|| federatedIdentities.values().stream().anyMatch(UserFederatedIdentityEntity::isUpdated);
}
public String getRealmId() {
return realmId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.updated |= !Objects.equals(this.username, username);
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.updated |= !Objects.equals(this.firstName, firstName);
this.firstName = firstName;
}
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long createdTimestamp) {
this.updated |= !Objects.equals(this.createdTimestamp, createdTimestamp);
this.createdTimestamp = createdTimestamp;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.updated |= !Objects.equals(this.lastName, lastName);
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email, boolean duplicateEmailsAllowed) {
this.updated |= !Objects.equals(this.email, email);
this.email = email;
this.emailConstraint = email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.updated |= !Objects.equals(this.enabled, enabled);
this.enabled = enabled;
}
public boolean isEmailVerified() {
return emailVerified;
}
public void setEmailVerified(boolean emailVerified) {
this.updated |= !Objects.equals(this.emailVerified, emailVerified);
this.emailVerified = emailVerified;
}
public String getEmailConstraint() {
return emailConstraint;
}
public void setEmailConstraint(String emailConstraint) {
this.updated |= !Objects.equals(this.emailConstraint, emailConstraint);
this.emailConstraint = emailConstraint;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public List<String> getAttribute(String name) {
return attributes.getOrDefault(name, Collections.emptyList());
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= !Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> value) {
this.updated |= !Objects.equals(this.attributes.put(name, value), value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public Set<String> getRequiredActions() {
return requiredActions;
}
public void setRequiredActions(Set<String> requiredActions) {
this.updated |= !Objects.equals(this.requiredActions, requiredActions);
this.requiredActions = requiredActions;
}
public void addRequiredAction(String requiredAction) {
this.updated |= this.requiredActions.add(requiredAction);
}
public void removeRequiredAction(String requiredAction) {
this.updated |= this.requiredActions.remove(requiredAction);
}
public void updateCredential(UserCredentialEntity credentialEntity) {
this.updated |= credentials.replace(credentialEntity.getId(), credentialEntity) != null;
}
public void addCredential(UserCredentialEntity credentialEntity) {
if (credentials.containsKey(credentialEntity.getId())) {
throw new ModelDuplicateException("A CredentialModel with given id already exists");
}
this.updated = true;
credentials.put(credentialEntity.getId(), credentialEntity);
credentialsOrder.add(credentialEntity.getId());
}
public boolean removeCredential(String credentialId) {
if (!credentials.containsKey(credentialId)) {
return false;
}
this.updated = true;
this.credentials.remove(credentialId);
this.credentialsOrder.remove(credentialId);
return true;
}
public UserCredentialEntity getCredential(String id) {
return credentials.get(id);
}
public Stream<UserCredentialEntity> getCredentials() {
return credentialsOrder.stream()
.map(credentials::get);
}
public int getCredentialIndex(String credentialId) {
return credentialsOrder.indexOf(credentialId);
}
public void moveCredential(int currentPosition, int newPosition) {
this.updated |= currentPosition != newPosition;
credentialsOrder.add(newPosition, credentialsOrder.remove(currentPosition));
}
public Stream<UserFederatedIdentityEntity> getFederatedIdentities() {
return federatedIdentities.values().stream();
}
public void setFederatedIdentities(Collection<UserFederatedIdentityEntity> federatedIdentities) {
this.updated = true;
this.federatedIdentities.clear();
this.federatedIdentities.putAll(federatedIdentities.stream()
.collect(Collectors.toMap(UserFederatedIdentityEntity::getIdentityProvider, Function.identity())));
}
public void addFederatedIdentity(UserFederatedIdentityEntity federatedIdentity) {
String idpId = federatedIdentity.getIdentityProvider();
this.updated |= !Objects.equals(this.federatedIdentities.put(idpId, federatedIdentity), federatedIdentity);
}
public UserFederatedIdentityEntity getFederatedIdentity(String federatedIdentity) {
return this.federatedIdentities.get(federatedIdentity);
}
public boolean removeFederatedIdentity(String providerId) {
boolean removed = federatedIdentities.remove(providerId) != null;
this.updated |= removed;
return removed;
}
public void updateFederatedIdentity(UserFederatedIdentityEntity federatedIdentityModel) {
this.updated |= federatedIdentities.replace(federatedIdentityModel.getIdentityProvider(), federatedIdentityModel) != null;
}
public Stream<UserConsentEntity> getUserConsents() {
return userConsents.values().stream();
}
public UserConsentEntity getUserConsent(String clientId) {
return this.userConsents.get(clientId);
}
public void addUserConsent(UserConsentEntity userConsentEntity) {
String clientId = userConsentEntity.getClientId();
this.updated |= !Objects.equals(this.userConsents.put(clientId, userConsentEntity), userConsentEntity);
}
public boolean removeUserConsent(String clientId) {
boolean removed = userConsents.remove(clientId) != null;
this.updated |= removed;
return removed;
}
public Set<String> getGroupsMembership() {
return groupsMembership;
}
public void setGroupsMembership(Set<String> groupsMembership) {
this.updated |= Objects.equals(groupsMembership, this.groupsMembership);
this.groupsMembership = groupsMembership;
}
public void addGroupsMembership(String groupId) {
this.updated |= this.groupsMembership.add(groupId);
}
public void removeGroupsMembership(String groupId) {
this.updated |= this.groupsMembership.remove(groupId);
}
public Set<String> getRolesMembership() {
return rolesMembership;
}
public void setRolesMembership(Set<String> rolesMembership) {
this.updated |= Objects.equals(rolesMembership, this.rolesMembership);
this.rolesMembership = rolesMembership;
}
public void addRolesMembership(String roleId) {
this.updated |= this.rolesMembership.add(roleId);
}
public void removeRolesMembership(String roleId) {
this.updated |= this.rolesMembership.remove(roleId);
}
public String getFederationLink() {
return federationLink;
}
public void setFederationLink(String federationLink) {
this.updated |= !Objects.equals(this.federationLink, federationLink);
this.federationLink = federationLink;
}
public String getServiceAccountClientLink() {
return serviceAccountClientLink;
}
public void setServiceAccountClientLink(String serviceAccountClientLink) {
this.updated |= !Objects.equals(this.serviceAccountClientLink, serviceAccountClientLink);
this.serviceAccountClientLink = serviceAccountClientLink;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.updated |= !Objects.equals(this.notBefore, notBefore);
this.notBefore = notBefore;
}
}

View file

@ -32,20 +32,14 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> {
public MapUserAdapter(KeycloakSession session, RealmModel realm, MapUserEntity entity) {
public abstract class MapUserAdapter<K> extends AbstractUserModel<MapUserEntity<K>> {
public MapUserAdapter(KeycloakSession session, RealmModel realm, MapUserEntity<K> entity) {
super(session, realm, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public String getUsername() {
return entity.getUsername();

View file

@ -17,18 +17,355 @@
package org.keycloak.models.map.user;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.UUID;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MapUserEntity extends AbstractUserEntity<UUID> {
/**
*
* @author mhajas
*/
public class MapUserEntity<K> implements AbstractEntity<K> {
public static final Comparator<MapUserEntity> COMPARE_BY_USERNAME = Comparator.comparing(MapUserEntity::getUsername, String.CASE_INSENSITIVE_ORDER);
private final K id;
private final String realmId;
private String username;
private String firstName;
private Long createdTimestamp;
private String lastName;
private String email;
private boolean enabled;
private boolean emailVerified;
// This is necessary to be able to dynamically switch unique email constraints on and off in the realm settings
private String emailConstraint = KeycloakModelUtils.generateId();
private Map<String, List<String>> attributes = new HashMap<>();
private Set<String> requiredActions = new HashSet<>();
private final Map<String, UserCredentialEntity> credentials = new HashMap<>();
private final List<String> credentialsOrder = new LinkedList<>();
private final Map<String, UserFederatedIdentityEntity> federatedIdentities = new HashMap<>();
private final Map<String, UserConsentEntity> userConsents = new HashMap<>();
private Set<String> groupsMembership = new HashSet<>();
private Set<String> rolesMembership = new HashSet<>();
private String federationLink;
private String serviceAccountClientLink;
private int notBefore;
static Comparator<MapUserEntity<?>> COMPARE_BY_USERNAME = Comparator.comparing(MapUserEntity::getUsername);
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
protected MapUserEntity() {
super();
this.id = null;
this.realmId = null;
}
public MapUserEntity(UUID id, String realmId) {
super(id, realmId);
public MapUserEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated
|| userConsents.values().stream().anyMatch(UserConsentEntity::isUpdated)
|| credentials.values().stream().anyMatch(UserCredentialEntity::isUpdated)
|| federatedIdentities.values().stream().anyMatch(UserFederatedIdentityEntity::isUpdated);
}
public String getRealmId() {
return realmId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.updated |= !Objects.equals(this.username, username);
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.updated |= !Objects.equals(this.firstName, firstName);
this.firstName = firstName;
}
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long createdTimestamp) {
this.updated |= !Objects.equals(this.createdTimestamp, createdTimestamp);
this.createdTimestamp = createdTimestamp;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.updated |= !Objects.equals(this.lastName, lastName);
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email, boolean duplicateEmailsAllowed) {
this.updated |= !Objects.equals(this.email, email);
this.email = email;
this.emailConstraint = email == null || duplicateEmailsAllowed ? KeycloakModelUtils.generateId() : email;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.updated |= !Objects.equals(this.enabled, enabled);
this.enabled = enabled;
}
public boolean isEmailVerified() {
return emailVerified;
}
public void setEmailVerified(boolean emailVerified) {
this.updated |= !Objects.equals(this.emailVerified, emailVerified);
this.emailVerified = emailVerified;
}
public String getEmailConstraint() {
return emailConstraint;
}
public void setEmailConstraint(String emailConstraint) {
this.updated |= !Objects.equals(this.emailConstraint, emailConstraint);
this.emailConstraint = emailConstraint;
}
public Map<String, List<String>> getAttributes() {
return attributes;
}
public List<String> getAttribute(String name) {
return attributes.getOrDefault(name, Collections.emptyList());
}
public void setAttributes(Map<String, List<String>> attributes) {
this.updated |= !Objects.equals(this.attributes, attributes);
this.attributes = attributes;
}
public void setAttribute(String name, List<String> value) {
this.updated |= !Objects.equals(this.attributes.put(name, value), value);
}
public void removeAttribute(String name) {
this.updated |= this.attributes.remove(name) != null;
}
public Set<String> getRequiredActions() {
return requiredActions;
}
public void setRequiredActions(Set<String> requiredActions) {
this.updated |= !Objects.equals(this.requiredActions, requiredActions);
this.requiredActions = requiredActions;
}
public void addRequiredAction(String requiredAction) {
this.updated |= this.requiredActions.add(requiredAction);
}
public void removeRequiredAction(String requiredAction) {
this.updated |= this.requiredActions.remove(requiredAction);
}
public void updateCredential(UserCredentialEntity credentialEntity) {
this.updated |= credentials.replace(credentialEntity.getId(), credentialEntity) != null;
}
public void addCredential(UserCredentialEntity credentialEntity) {
if (credentials.containsKey(credentialEntity.getId())) {
throw new ModelDuplicateException("A CredentialModel with given id already exists");
}
this.updated = true;
credentials.put(credentialEntity.getId(), credentialEntity);
credentialsOrder.add(credentialEntity.getId());
}
public boolean removeCredential(String credentialId) {
if (!credentials.containsKey(credentialId)) {
return false;
}
this.updated = true;
this.credentials.remove(credentialId);
this.credentialsOrder.remove(credentialId);
return true;
}
public UserCredentialEntity getCredential(String id) {
return credentials.get(id);
}
public Stream<UserCredentialEntity> getCredentials() {
return credentialsOrder.stream()
.map(credentials::get);
}
public int getCredentialIndex(String credentialId) {
return credentialsOrder.indexOf(credentialId);
}
public void moveCredential(int currentPosition, int newPosition) {
this.updated |= currentPosition != newPosition;
credentialsOrder.add(newPosition, credentialsOrder.remove(currentPosition));
}
public Stream<UserFederatedIdentityEntity> getFederatedIdentities() {
return federatedIdentities.values().stream();
}
public void setFederatedIdentities(Collection<UserFederatedIdentityEntity> federatedIdentities) {
this.updated = true;
this.federatedIdentities.clear();
this.federatedIdentities.putAll(federatedIdentities.stream()
.collect(Collectors.toMap(UserFederatedIdentityEntity::getIdentityProvider, Function.identity())));
}
public void addFederatedIdentity(UserFederatedIdentityEntity federatedIdentity) {
String idpId = federatedIdentity.getIdentityProvider();
this.updated |= !Objects.equals(this.federatedIdentities.put(idpId, federatedIdentity), federatedIdentity);
}
public UserFederatedIdentityEntity getFederatedIdentity(String federatedIdentity) {
return this.federatedIdentities.get(federatedIdentity);
}
public boolean removeFederatedIdentity(String providerId) {
boolean removed = federatedIdentities.remove(providerId) != null;
this.updated |= removed;
return removed;
}
public void updateFederatedIdentity(UserFederatedIdentityEntity federatedIdentityModel) {
this.updated |= federatedIdentities.replace(federatedIdentityModel.getIdentityProvider(), federatedIdentityModel) != null;
}
public Stream<UserConsentEntity> getUserConsents() {
return userConsents.values().stream();
}
public UserConsentEntity getUserConsent(String clientId) {
return this.userConsents.get(clientId);
}
public void addUserConsent(UserConsentEntity userConsentEntity) {
String clientId = userConsentEntity.getClientId();
this.updated |= !Objects.equals(this.userConsents.put(clientId, userConsentEntity), userConsentEntity);
}
public boolean removeUserConsent(String clientId) {
boolean removed = userConsents.remove(clientId) != null;
this.updated |= removed;
return removed;
}
public Set<String> getGroupsMembership() {
return groupsMembership;
}
public void setGroupsMembership(Set<String> groupsMembership) {
this.updated |= Objects.equals(groupsMembership, this.groupsMembership);
this.groupsMembership = groupsMembership;
}
public void addGroupsMembership(String groupId) {
this.updated |= this.groupsMembership.add(groupId);
}
public void removeGroupsMembership(String groupId) {
this.updated |= this.groupsMembership.remove(groupId);
}
public Set<String> getRolesMembership() {
return rolesMembership;
}
public void setRolesMembership(Set<String> rolesMembership) {
this.updated |= Objects.equals(rolesMembership, this.rolesMembership);
this.rolesMembership = rolesMembership;
}
public void addRolesMembership(String roleId) {
this.updated |= this.rolesMembership.add(roleId);
}
public void removeRolesMembership(String roleId) {
this.updated |= this.rolesMembership.remove(roleId);
}
public String getFederationLink() {
return federationLink;
}
public void setFederationLink(String federationLink) {
this.updated |= !Objects.equals(this.federationLink, federationLink);
this.federationLink = federationLink;
}
public String getServiceAccountClientLink() {
return serviceAccountClientLink;
}
public void setServiceAccountClientLink(String serviceAccountClientLink) {
this.updated |= !Objects.equals(this.serviceAccountClientLink, serviceAccountClientLink);
this.serviceAccountClientLink = serviceAccountClientLink;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.updated |= !Objects.equals(this.notBefore, notBefore);
this.notBefore = notBefore;
}
}

View file

@ -58,7 +58,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@ -73,30 +72,33 @@ import static org.keycloak.models.UserModel.LAST_NAME;
import static org.keycloak.models.UserModel.USERNAME;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
public class MapUserProvider implements UserProvider.Streams, UserCredentialStore.Streams {
public class MapUserProvider<K> implements UserProvider.Streams, UserCredentialStore.Streams {
private static final Logger LOG = Logger.getLogger(MapUserProvider.class);
private static final Predicate<MapUserEntity> ALWAYS_FALSE = c -> { return false; };
private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapUserEntity, UserModel> tx;
private final MapStorage<UUID, MapUserEntity, UserModel> userStore;
final MapKeycloakTransaction<K, MapUserEntity<K>, UserModel> tx;
private final MapStorage<K, MapUserEntity<K>, UserModel> userStore;
public MapUserProvider(KeycloakSession session, MapStorage<UUID, MapUserEntity, UserModel> store) {
public MapUserProvider(KeycloakSession session, MapStorage<K, MapUserEntity<K>, UserModel> store) {
this.session = session;
this.userStore = store;
this.tx = userStore.createTransaction(session);
session.getTransactionManager().enlist(tx);
}
private MapUserEntity registerEntityForChanges(MapUserEntity origEntity) {
MapUserEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapUserEntity::isUpdated);
private MapUserEntity<K> registerEntityForChanges(MapUserEntity<K> origEntity) {
MapUserEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapUserEntity<K>::isUpdated);
return res;
}
private Function<MapUserEntity, UserModel> entityToAdapterFunc(RealmModel realm) {
private Function<MapUserEntity<K>, UserModel> entityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return origEntity -> new MapUserAdapter(session, realm, registerEntityForChanges(origEntity)) {
return origEntity -> new MapUserAdapter<K>(session, realm, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return userStore.getKeyConvertor().keyToString(entity.getId());
}
@Override
public boolean checkEmailUniqueness(RealmModel realm, String email) {
@ -110,9 +112,9 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
};
}
private Predicate<MapUserEntity> entityRealmFilter(RealmModel realm) {
private Predicate<MapUserEntity<K>> entityRealmFilter(RealmModel realm) {
if (realm == null || realm.getId() == null) {
return MapUserProvider.ALWAYS_FALSE;
return c -> false;
}
String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId());
@ -122,22 +124,22 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
return new ModelException("Specified user doesn't exist.");
}
private Optional<MapUserEntity> getEntityById(RealmModel realm, String id) {
private Optional<MapUserEntity<K>> getEntityById(RealmModel realm, String id) {
try {
return getEntityById(realm, UUID.fromString(id));
return getEntityById(realm, userStore.getKeyConvertor().fromString(id));
} catch (IllegalArgumentException ex) {
return Optional.empty();
}
}
private MapUserEntity getRegisteredEntityByIdOrThrow(RealmModel realm, String id) {
private MapUserEntity<K> getRegisteredEntityByIdOrThrow(RealmModel realm, String id) {
return getEntityById(realm, id)
.map(this::registerEntityForChanges)
.orElseThrow(this::userDoesntExistException);
}
private Optional<MapUserEntity> getEntityById(RealmModel realm, UUID id) {
MapUserEntity mapUserEntity = tx.read(id);
private Optional<MapUserEntity<K>> getEntityById(RealmModel realm, K id) {
MapUserEntity<K> mapUserEntity = tx.read(id);
if (mapUserEntity != null && entityRealmFilter(realm).test(mapUserEntity)) {
return Optional.of(mapUserEntity);
}
@ -145,7 +147,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
return Optional.empty();
}
private Optional<MapUserEntity> getRegisteredEntityById(RealmModel realm, String id) {
private Optional<MapUserEntity<K>> getRegisteredEntityById(RealmModel realm, String id) {
return getEntityById(realm, id).map(this::registerEntityForChanges);
}
@ -193,7 +195,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public Stream<FederatedIdentityModel> getFederatedIdentitiesStream(RealmModel realm, UserModel user) {
LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId())
.map(AbstractUserEntity::getFederatedIdentities).orElseGet(Stream::empty)
.map(MapUserEntity::getFederatedIdentities).orElseGet(Stream::empty)
.map(UserFederatedIdentityEntity::toModel);
}
@ -249,7 +251,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public Stream<UserConsentModel> getConsentsStream(RealmModel realm, String userId) {
LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace());
return getEntityById(realm, userId)
.map(AbstractUserEntity::getUserConsents)
.map(MapUserEntity::getUserConsents)
.orElse(Stream.empty())
.map(consent -> UserConsentEntity.toModel(realm, consent));
}
@ -258,7 +260,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) {
LOG.tracef("updateConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace());
MapUserEntity user = getRegisteredEntityByIdOrThrow(realm, userId);
MapUserEntity<K> user = getRegisteredEntityByIdOrThrow(realm, userId);
UserConsentEntity userConsentEntity = user.getUserConsent(consent.getClient().getId());
if (userConsentEntity == null) {
throw new ModelException("Consent not found for client [" + consent.getClient().getId() + "] and user [" + userId + "]");
@ -329,13 +331,13 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
throw new ModelDuplicateException("User with username '" + username + "' in realm " + realm.getName() + " already exists" );
}
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final K entityId = id == null ? userStore.getKeyConvertor().yieldNewUniqueKey() : userStore.getKeyConvertor().fromString(id);
if (tx.read(entityId) != null) {
throw new ModelDuplicateException("User exists: " + entityId);
}
MapUserEntity entity = new MapUserEntity(entityId, realm.getId());
MapUserEntity<K> entity = new MapUserEntity<>(entityId, realm.getId());
entity.setUsername(username.toLowerCase());
entity.setCreatedTimestamp(Time.currentTimeMillis());
@ -366,7 +368,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
ModelCriteriaBuilder<UserModel> mcb = userStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
tx.delete(UUID.randomUUID(), mcb);
tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override
@ -376,7 +378,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId);
tx.delete(UUID.randomUUID(), mcb);
tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override
@ -386,7 +388,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.setFederationLink(null));
}
@ -400,7 +402,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, roleId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.removeRolesMembership(roleId));
}
@ -414,7 +416,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.ASSIGNED_GROUP, Operator.EQ, groupId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.removeGroupsMembership(groupId));
}
@ -428,7 +430,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.CONSENT_FOR_CLIENT, Operator.EQ, clientId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.removeUserConsent(clientId));
}
@ -448,8 +450,8 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, clientScope.getRealm().getId())
.compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
s.flatMap(AbstractUserEntity::getUserConsents)
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.flatMap(MapUserEntity::getUserConsents)
.forEach(consent -> consent.removeGrantedClientScopesIds(clientScopeId));
}
}
@ -466,14 +468,14 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, Operator.EQ, componentId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
String providerIdS = new StorageId(componentId, "").getId();
s.forEach(removeConsentsForExternalClient(providerIdS));
}
}
}
private Consumer<MapUserEntity> removeConsentsForExternalClient(String idPrefix) {
private Consumer<MapUserEntity<K>> removeConsentsForExternalClient(String idPrefix) {
return userEntity -> {
List<String> consentClientIds = userEntity.getUserConsents()
.map(UserConsentEntity::getClientId)
@ -494,7 +496,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
ModelCriteriaBuilder<UserModel> mcb = userStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.map(this::registerEntityForChanges)
.forEach(entity -> entity.addRolesMembership(roleId));
}
@ -514,7 +516,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.USERNAME, Operator.ILIKE, username);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) {
try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
return s.findFirst()
.map(entityToAdapterFunc(realm)).orElse(null);
}
@ -527,7 +529,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.EMAIL, Operator.EQ, email);
List<MapUserEntity> usersWithEmail = tx.getUpdatedNotRemoved(mcb)
List<MapUserEntity<K>> usersWithEmail = tx.getUpdatedNotRemoved(mcb)
.filter(userEntity -> Objects.equals(userEntity.getEmail(), email))
.collect(Collectors.toList());
if (usersWithEmail.isEmpty()) return null;
@ -538,7 +540,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
throw new ModelDuplicateException("Multiple users with email '" + email + "' exist in Keycloak.");
}
MapUserEntity userEntity = registerEntityForChanges(usersWithEmail.get(0));
MapUserEntity<K> userEntity = registerEntityForChanges(usersWithEmail.get(0));
if (!realm.isDuplicateEmailsAllowed()) {
if (userEntity.getEmail() != null && !userEntity.getEmail().equals(userEntity.getEmailConstraint())) {
@ -548,7 +550,12 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
}
}
return new MapUserAdapter(session, realm, userEntity) {
return new MapUserAdapter<K>(session, realm, userEntity) {
@Override
public String getId() {
return userStore.getKeyConvertor().keyToString(userEntity.getId());
}
@Override
public boolean checkEmailUniqueness(RealmModel realm, String email) {
return getUserByEmail(realm, email) != null;
@ -700,8 +707,8 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
mcb = mcb.compare(SearchableFields.ASSIGNED_GROUP, Operator.IN, authorizedGroups);
}
Stream<MapUserEntity> usersStream = tx.getUpdatedNotRemoved(mcb)
.sorted(AbstractUserEntity.COMPARE_BY_USERNAME); // Sort before paginating
Stream<MapUserEntity<K>> usersStream = tx.getUpdatedNotRemoved(mcb)
.sorted(MapUserEntity.COMPARE_BY_USERNAME); // Sort before paginating
return paginatedStream(usersStream, firstResult, maxResults) // paginate if necessary
.map(entityToAdapterFunc(realm))
@ -739,9 +746,9 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
String userId = user.getId();
Optional<MapUserEntity> userById = getEntityById(realm, userId);
Optional<MapUserEntity<K>> userById = getEntityById(realm, userId);
if (userById.isPresent()) {
tx.delete(UUID.fromString(userId));
tx.delete(userStore.getKeyConvertor().fromString(userId));
return true;
}
@ -766,7 +773,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.ifPresent(updateCredential(cred));
}
private Consumer<MapUserEntity> updateCredential(CredentialModel credentialModel) {
private Consumer<MapUserEntity<K>> updateCredential(CredentialModel credentialModel) {
return user -> {
UserCredentialEntity credentialEntity = user.getCredential(credentialModel.getId());
if (credentialEntity == null) return;
@ -811,7 +818,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId())
.map(AbstractUserEntity::getCredentials)
.map(MapUserEntity::getCredentials)
.orElseGet(Stream::empty)
.map(UserCredentialEntity::toModel);
}
@ -835,7 +842,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId) {
LOG.tracef("moveCredentialTo(%s, %s, %s, %s)%s", realm, user.getId(), id, newPreviousCredentialId, getShortStackTrace());
String userId = user.getId();
MapUserEntity userEntity = getRegisteredEntityById(realm, userId).orElse(null);
MapUserEntity<K> userEntity = getRegisteredEntityById(realm, userId).orElse(null);
if (userEntity == null) {
LOG.warnf("User with id: [%s] not found", userId);
return false;

View file

@ -18,34 +18,29 @@
package org.keycloak.models.map.user;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.UserProviderFactory;
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 java.util.UUID;
/**
*
* @author mhajas
*/
public class MapUserProviderFactory extends AbstractMapProviderFactory<UserProvider> implements UserProviderFactory {
public class MapUserProviderFactory<K> extends AbstractMapProviderFactory<UserProvider, K, MapUserEntity<K>, UserModel> implements UserProviderFactory {
private MapStorage<UUID, MapUserEntity, UserModel> store;
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("users", UUID.class, MapUserEntity.class, UserModel.class);
public MapUserProviderFactory() {
super(MapUserEntity.class, UserModel.class);
}
@Override
public UserProvider create(KeycloakSession session) {
return new MapUserProvider(session, store);
return new MapUserProvider<>(session, getStorage(session));
}
@Override
public String getHelpText() {
return "User provider";
}
}

View file

@ -1,205 +0,0 @@
/*
* Copyright 2021 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.userSession;
import org.keycloak.common.util.Time;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractAuthenticatedClientSessionEntity<K> implements AbstractEntity<K> {
private K id;
private String userSessionId;
private String realmId;
private String clientId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private String authMethod;
private String redirectUri;
private volatile int timestamp;
private long expiration;
private String action;
private Map<String, String> notes = new ConcurrentHashMap<>();
private String currentRefreshToken;
private int currentRefreshTokenUseCount;
private boolean offline;
public AbstractAuthenticatedClientSessionEntity() {
this.id = null;
this.realmId = null;
}
public AbstractAuthenticatedClientSessionEntity(K id, String userSessionId, String realmId, String clientId, boolean offline) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(userSessionId, "userSessionId");
Objects.requireNonNull(realmId, "realmId");
Objects.requireNonNull(clientId, "clientId");
this.id = id;
this.userSessionId = userSessionId;
this.realmId = realmId;
this.clientId = clientId;
this.offline = offline;
this.timestamp = Time.currentTime();
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= !Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.updated |= !Objects.equals(this.userSessionId, userSessionId);
this.userSessionId = userSessionId;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.updated |= !Objects.equals(this.authMethod, authMethod);
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.updated |= !Objects.equals(this.redirectUri, redirectUri);
this.redirectUri = redirectUri;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.updated |= this.timestamp != timestamp;
this.timestamp = timestamp;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.updated |= this.expiration != expiration;
this.expiration = expiration;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.updated |= !Objects.equals(this.action, action);
this.action = action;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.updated |= !Objects.equals(this.notes, notes);
this.notes = notes;
}
public String removeNote(String name) {
String note = this.notes.remove(name);
this.updated |= note != null;
return note;
}
public void addNote(String name, String value) {
this.updated |= !Objects.equals(this.notes.put(name, value), value);
}
public String getCurrentRefreshToken() {
return currentRefreshToken;
}
public void setCurrentRefreshToken(String currentRefreshToken) {
this.updated |= !Objects.equals(this.currentRefreshToken, currentRefreshToken);
this.currentRefreshToken = currentRefreshToken;
}
public int getCurrentRefreshTokenUseCount() {
return currentRefreshTokenUseCount;
}
public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
this.updated |= this.currentRefreshTokenUseCount != currentRefreshTokenUseCount;
this.currentRefreshTokenUseCount = currentRefreshTokenUseCount;
}
public boolean isOffline() {
return offline;
}
public void setOffline(boolean offline) {
this.updated |= this.offline != offline;
this.offline = offline;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
}

View file

@ -28,15 +28,15 @@ import java.util.Objects;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractAuthenticatedClientSessionModel<E extends AbstractEntity> implements AuthenticatedClientSessionModel {
public abstract class AbstractAuthenticatedClientSessionModel<K> implements AuthenticatedClientSessionModel {
protected final KeycloakSession session;
protected final RealmModel realm;
protected ClientModel client;
protected UserSessionModel userSession;
protected final E entity;
protected final MapAuthenticatedClientSessionEntity<K> entity;
public AbstractAuthenticatedClientSessionModel(KeycloakSession session, RealmModel realm, ClientModel client,
UserSessionModel userSession, E entity) {
UserSessionModel userSession, MapAuthenticatedClientSessionEntity<K> entity) {
Objects.requireNonNull(entity, "entity");
Objects.requireNonNull(realm, "realm");
Objects.requireNonNull(client, "client");

View file

@ -1,288 +0,0 @@
/*
* Copyright 2021 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.userSession;
import org.keycloak.common.util.Time;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractUserSessionEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private String userId;
private String brokerSessionId;
private String brokerUserId;
private String loginUsername;
private String ipAddress;
private String authMethod;
private boolean rememberMe;
private int started;
private int lastSessionRefresh;
private long expiration;
private Map<String, String> notes = new ConcurrentHashMap<>();
private UserSessionModel.State state;
private UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.PERSISTENT;
private Map<String, K> authenticatedClientSessions = new ConcurrentHashMap<>();
private boolean offline;
public AbstractUserSessionEntity() {
this.id = null;
this.realmId = null;
}
public AbstractUserSessionEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
public AbstractUserSessionEntity(K id, RealmModel realm, UserModel user, String loginUsername, String ipAddress,
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId,
boolean offline) {
this.id = id;
this.realmId = realm.getId();
this.userId = user.getId();
this.loginUsername = loginUsername;
this.ipAddress = ipAddress;
this.authMethod = authMethod;
this.rememberMe = rememberMe;
this.brokerSessionId = brokerSessionId;
this.brokerUserId = brokerUserId;
this.started = Time.currentTime();
this.lastSessionRefresh = started;
this.offline = offline;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.updated |= !Objects.equals(this.userId, userId);
this.userId = userId;
}
public String getBrokerSessionId() {
return brokerSessionId;
}
public void setBrokerSessionId(String brokerSessionId) {
this.updated |= !Objects.equals(this.brokerSessionId, brokerSessionId);
this.brokerSessionId = brokerSessionId;
}
public String getBrokerUserId() {
return brokerUserId;
}
public void setBrokerUserId(String brokerUserId) {
this.updated |= !Objects.equals(this.brokerUserId, brokerUserId);
this.brokerUserId = brokerUserId;
}
public String getLoginUsername() {
return loginUsername;
}
public void setLoginUsername(String loginUsername) {
this.updated |= !Objects.equals(this.loginUsername, loginUsername);
this.loginUsername = loginUsername;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.updated |= !Objects.equals(this.ipAddress, ipAddress);
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.updated |= !Objects.equals(this.authMethod, authMethod);
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.updated |= this.rememberMe != rememberMe;
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}
public void setStarted(int started) {
this.updated |= this.started != started;
this.started = started;
}
public int getLastSessionRefresh() {
return lastSessionRefresh;
}
public void setLastSessionRefresh(int lastSessionRefresh) {
this.updated |= this.lastSessionRefresh != lastSessionRefresh;
this.lastSessionRefresh = lastSessionRefresh;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.updated |= this.expiration != expiration;
this.expiration = expiration;
}
public Map<String, String> getNotes() {
return notes;
}
public String getNote(String name) {
return notes.get(name);
}
public void setNotes(Map<String, String> notes) {
this.updated |= !Objects.equals(this.notes, notes);
this.notes = notes;
}
public String removeNote(String name) {
String note = this.notes.remove(name);
this.updated |= note != null;
return note;
}
public void addNote(String name, String value) {
this.updated |= !Objects.equals(this.notes.put(name, value), value);
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.updated |= !Objects.equals(this.state, state);
this.state = state;
}
public Map<String, K> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
public void setAuthenticatedClientSessions(Map<String, K> authenticatedClientSessions) {
this.updated |= !Objects.equals(this.authenticatedClientSessions, authenticatedClientSessions);
this.authenticatedClientSessions = authenticatedClientSessions;
}
public void addAuthenticatedClientSession(String clientId, K clientSessionId) {
this.updated |= !Objects.equals(this.authenticatedClientSessions.put(clientId, clientSessionId), clientSessionId);
}
public K removeAuthenticatedClientSession(String clientId) {
K entity = this.authenticatedClientSessions.remove(clientId);
this.updated |= entity != null;
return entity;
}
public void clearAuthenticatedClientSessions() {
this.updated |= !authenticatedClientSessions.isEmpty();
this.authenticatedClientSessions.clear();
}
public boolean isOffline() {
return offline;
}
public void setOffline(boolean offline) {
this.updated |= this.offline != offline;
this.offline = offline;
}
public UserSessionModel.SessionPersistenceState getPersistenceState() {
return persistenceState;
}
public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) {
this.updated |= !Objects.equals(this.persistenceState, persistenceState);
this.persistenceState = persistenceState;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
}

View file

@ -26,12 +26,12 @@ import java.util.Objects;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractUserSessionModel<E extends AbstractEntity> implements UserSessionModel {
public abstract class AbstractUserSessionModel<K> implements UserSessionModel {
protected final KeycloakSession session;
protected final RealmModel realm;
protected final E entity;
protected final MapUserSessionEntity<K> entity;
public AbstractUserSessionModel(KeycloakSession session, RealmModel realm, E entity) {
public AbstractUserSessionModel(KeycloakSession session, RealmModel realm, MapUserSessionEntity<K> entity) {
Objects.requireNonNull(entity, "entity");
Objects.requireNonNull(realm, "realm");
@ -39,18 +39,4 @@ public abstract class AbstractUserSessionModel<E extends AbstractEntity> impleme
this.realm = realm;
this.entity = entity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserSessionModel)) return false;
UserSessionModel that = (UserSessionModel) o;
return Objects.equals(that.getId(), getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -26,18 +26,13 @@ import java.util.Map;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class MapAuthenticatedClientSessionAdapter extends AbstractAuthenticatedClientSessionModel<MapAuthenticatedClientSessionEntity> {
public abstract class MapAuthenticatedClientSessionAdapter<K> extends AbstractAuthenticatedClientSessionModel<K> {
public MapAuthenticatedClientSessionAdapter(KeycloakSession session, RealmModel realm, ClientModel client,
UserSessionModel userSession, MapAuthenticatedClientSessionEntity entity) {
UserSessionModel userSession, MapAuthenticatedClientSessionEntity<K> entity) {
super(session, realm, client, userSession, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public int getTimestamp() {
return entity.getTimestamp();

View file

@ -16,18 +16,190 @@
*/
package org.keycloak.models.map.userSession;
import java.util.UUID;
import org.keycloak.common.util.Time;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapAuthenticatedClientSessionEntity extends AbstractAuthenticatedClientSessionEntity<UUID> {
public class MapAuthenticatedClientSessionEntity<K> implements AbstractEntity<K> {
protected MapAuthenticatedClientSessionEntity() {
super();
private K id;
private String userSessionId;
private String realmId;
private String clientId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private String authMethod;
private String redirectUri;
private volatile int timestamp;
private long expiration;
private String action;
private Map<String, String> notes = new ConcurrentHashMap<>();
private String currentRefreshToken;
private int currentRefreshTokenUseCount;
private boolean offline;
public MapAuthenticatedClientSessionEntity() {
this.id = null;
this.realmId = null;
}
public MapAuthenticatedClientSessionEntity(UUID id, String userSessionId, String realmId, String clientId, boolean offline) {
super(id, userSessionId, realmId, clientId, offline);
public MapAuthenticatedClientSessionEntity(K id, String userSessionId, String realmId, String clientId, boolean offline) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(userSessionId, "userSessionId");
Objects.requireNonNull(realmId, "realmId");
Objects.requireNonNull(clientId, "clientId");
this.id = id;
this.userSessionId = userSessionId;
this.realmId = realmId;
this.clientId = clientId;
this.offline = offline;
this.timestamp = Time.currentTime();
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.updated |= !Objects.equals(this.clientId, clientId);
this.clientId = clientId;
}
public String getUserSessionId() {
return userSessionId;
}
public void setUserSessionId(String userSessionId) {
this.updated |= !Objects.equals(this.userSessionId, userSessionId);
this.userSessionId = userSessionId;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.updated |= !Objects.equals(this.authMethod, authMethod);
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.updated |= !Objects.equals(this.redirectUri, redirectUri);
this.redirectUri = redirectUri;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.updated |= this.timestamp != timestamp;
this.timestamp = timestamp;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.updated |= this.expiration != expiration;
this.expiration = expiration;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.updated |= !Objects.equals(this.action, action);
this.action = action;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.updated |= !Objects.equals(this.notes, notes);
this.notes = notes;
}
public String removeNote(String name) {
String note = this.notes.remove(name);
this.updated |= note != null;
return note;
}
public void addNote(String name, String value) {
this.updated |= !Objects.equals(this.notes.put(name, value), value);
}
public String getCurrentRefreshToken() {
return currentRefreshToken;
}
public void setCurrentRefreshToken(String currentRefreshToken) {
this.updated |= !Objects.equals(this.currentRefreshToken, currentRefreshToken);
this.currentRefreshToken = currentRefreshToken;
}
public int getCurrentRefreshTokenUseCount() {
return currentRefreshTokenUseCount;
}
public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
this.updated |= this.currentRefreshTokenUseCount != currentRefreshTokenUseCount;
this.currentRefreshTokenUseCount = currentRefreshTokenUseCount;
}
public boolean isOffline() {
return offline;
}
public void setOffline(boolean offline) {
this.updated |= this.offline != offline;
this.offline = offline;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
}

View file

@ -23,28 +23,25 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class MapUserSessionAdapter extends AbstractUserSessionModel<MapUserSessionEntity> {
public abstract class MapUserSessionAdapter<K> extends AbstractUserSessionModel<K> {
public MapUserSessionAdapter(KeycloakSession session, RealmModel realm, MapUserSessionEntity entity) {
public MapUserSessionAdapter(KeycloakSession session, RealmModel realm, MapUserSessionEntity<K> entity) {
super(session, realm, entity);
}
@Override
public String getId() {
return entity.getId().toString();
}
@Override
public RealmModel getRealm() {
return realm;
@ -134,7 +131,7 @@ public abstract class MapUserSessionAdapter extends AbstractUserSessionModel<Map
@Override
public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) {
UUID clientSessionId = entity.getAuthenticatedClientSessions().get(clientUUID);
String clientSessionId = entity.getAuthenticatedClientSessions().get(clientUUID);
if (clientSessionId == null) {
return null;
@ -220,4 +217,18 @@ public abstract class MapUserSessionAdapter extends AbstractUserSessionModel<Map
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserSessionModel)) return false;
UserSessionModel that = (UserSessionModel) o;
return Objects.equals(that.getId(), getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -16,26 +16,273 @@
*/
package org.keycloak.models.map.userSession;
import org.keycloak.common.util.Time;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.UUID;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserSessionEntity extends AbstractUserSessionEntity<UUID> {
protected MapUserSessionEntity() {
super();
public class MapUserSessionEntity<K> implements AbstractEntity<K> {
private K id;
private String realmId;
/**
* Flag signalizing that any of the setters has been meaningfully used.
*/
protected boolean updated;
private String userId;
private String brokerSessionId;
private String brokerUserId;
private String loginUsername;
private String ipAddress;
private String authMethod;
private boolean rememberMe;
private int started;
private int lastSessionRefresh;
private long expiration;
private Map<String, String> notes = new ConcurrentHashMap<>();
private UserSessionModel.State state;
private UserSessionModel.SessionPersistenceState persistenceState = UserSessionModel.SessionPersistenceState.PERSISTENT;
private Map<String, String> authenticatedClientSessions = new ConcurrentHashMap<>();
private boolean offline;
public MapUserSessionEntity() {
this.id = null;
this.realmId = null;
}
public MapUserSessionEntity(UUID id, String realmId) {
super(id, realmId);
public MapUserSessionEntity(K id, String realmId) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
}
public MapUserSessionEntity(UUID id, RealmModel realm, UserModel user, String loginUsername, String ipAddress,
public MapUserSessionEntity(K id, RealmModel realm, UserModel user, String loginUsername, String ipAddress,
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId,
boolean offline) {
super(id, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, offline);
this.id = id;
this.realmId = realm.getId();
this.userId = user.getId();
this.loginUsername = loginUsername;
this.ipAddress = ipAddress;
this.authMethod = authMethod;
this.rememberMe = rememberMe;
this.brokerSessionId = brokerSessionId;
this.brokerUserId = brokerUserId;
this.started = Time.currentTime();
this.lastSessionRefresh = started;
this.offline = offline;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.updated |= !Objects.equals(this.userId, userId);
this.userId = userId;
}
public String getBrokerSessionId() {
return brokerSessionId;
}
public void setBrokerSessionId(String brokerSessionId) {
this.updated |= !Objects.equals(this.brokerSessionId, brokerSessionId);
this.brokerSessionId = brokerSessionId;
}
public String getBrokerUserId() {
return brokerUserId;
}
public void setBrokerUserId(String brokerUserId) {
this.updated |= !Objects.equals(this.brokerUserId, brokerUserId);
this.brokerUserId = brokerUserId;
}
public String getLoginUsername() {
return loginUsername;
}
public void setLoginUsername(String loginUsername) {
this.updated |= !Objects.equals(this.loginUsername, loginUsername);
this.loginUsername = loginUsername;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.updated |= !Objects.equals(this.ipAddress, ipAddress);
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.updated |= !Objects.equals(this.authMethod, authMethod);
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.updated |= this.rememberMe != rememberMe;
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}
public void setStarted(int started) {
this.updated |= this.started != started;
this.started = started;
}
public int getLastSessionRefresh() {
return lastSessionRefresh;
}
public void setLastSessionRefresh(int lastSessionRefresh) {
this.updated |= this.lastSessionRefresh != lastSessionRefresh;
this.lastSessionRefresh = lastSessionRefresh;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.updated |= this.expiration != expiration;
this.expiration = expiration;
}
public Map<String, String> getNotes() {
return notes;
}
public String getNote(String name) {
return notes.get(name);
}
public void setNotes(Map<String, String> notes) {
this.updated |= !Objects.equals(this.notes, notes);
this.notes = notes;
}
public String removeNote(String name) {
String note = this.notes.remove(name);
this.updated |= note != null;
return note;
}
public void addNote(String name, String value) {
this.updated |= !Objects.equals(this.notes.put(name, value), value);
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.updated |= !Objects.equals(this.state, state);
this.state = state;
}
public Map<String, String> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
public void setAuthenticatedClientSessions(Map<String, String> authenticatedClientSessions) {
this.updated |= !Objects.equals(this.authenticatedClientSessions, authenticatedClientSessions);
this.authenticatedClientSessions = authenticatedClientSessions;
}
public void addAuthenticatedClientSession(String clientId, String clientSessionId) {
this.updated |= !Objects.equals(this.authenticatedClientSessions.put(clientId, clientSessionId), clientSessionId);
}
public String removeAuthenticatedClientSession(String clientId) {
String entity = this.authenticatedClientSessions.remove(clientId);
this.updated |= entity != null;
return entity;
}
public void clearAuthenticatedClientSessions() {
this.updated |= !authenticatedClientSessions.isEmpty();
this.authenticatedClientSessions.clear();
}
public boolean isOffline() {
return offline;
}
public void setOffline(boolean offline) {
this.updated |= this.offline != offline;
this.offline = offline;
}
public UserSessionModel.SessionPersistenceState getPersistenceState() {
return persistenceState;
}
public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) {
this.updated |= !Objects.equals(this.persistenceState, persistenceState);
this.persistenceState = persistenceState;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
}
}

View file

@ -39,7 +39,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
@ -56,22 +55,22 @@ import static org.keycloak.utils.StreamsUtil.paginatedStream;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserSessionProvider implements UserSessionProvider {
public class MapUserSessionProvider<UK, CK> implements UserSessionProvider {
private static final Logger LOG = Logger.getLogger(MapUserSessionProvider.class);
private final KeycloakSession session;
protected final MapKeycloakTransaction<UUID, MapUserSessionEntity, UserSessionModel> userSessionTx;
protected final MapKeycloakTransaction<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionTx;
private final MapStorage<UUID, MapUserSessionEntity, UserSessionModel> userSessionStore;
private final MapStorage<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore;
protected final MapKeycloakTransaction<UK, MapUserSessionEntity<UK>, UserSessionModel> userSessionTx;
protected final MapKeycloakTransaction<CK, MapAuthenticatedClientSessionEntity<CK>, AuthenticatedClientSessionModel> clientSessionTx;
private final MapStorage<UK, MapUserSessionEntity<UK>, UserSessionModel> userSessionStore;
private final MapStorage<CK, MapAuthenticatedClientSessionEntity<CK>, AuthenticatedClientSessionModel> clientSessionStore;
/**
* Storage for transient user sessions which lifespan is limited to one request.
*/
private final Map<UUID, MapUserSessionEntity> transientUserSessions = new HashMap<>();
private final Map<UK, MapUserSessionEntity<UK>> transientUserSessions = new HashMap<>();
public MapUserSessionProvider(KeycloakSession session, MapStorage<UUID, MapUserSessionEntity, UserSessionModel> userSessionStore,
MapStorage<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore) {
public MapUserSessionProvider(KeycloakSession session, MapStorage<UK, MapUserSessionEntity<UK>, UserSessionModel> userSessionStore,
MapStorage<CK, MapAuthenticatedClientSessionEntity<CK>, AuthenticatedClientSessionModel> clientSessionStore) {
this.session = session;
this.userSessionStore = userSessionStore;
this.clientSessionStore = clientSessionStore;
@ -82,7 +81,7 @@ public class MapUserSessionProvider implements UserSessionProvider {
session.getTransactionManager().enlistAfterCompletion(clientSessionTx);
}
private Function<MapUserSessionEntity, UserSessionModel> userEntityToAdapterFunc(RealmModel realm) {
private Function<MapUserSessionEntity<UK>, UserSessionModel> userEntityToAdapterFunc(RealmModel realm) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
return (origEntity) -> {
if (origEntity.getExpiration() <= Time.currentTime()) {
@ -92,12 +91,16 @@ public class MapUserSessionProvider implements UserSessionProvider {
userSessionTx.delete(origEntity.getId());
return null;
} else {
return new MapUserSessionAdapter(session, realm,
return new MapUserSessionAdapter<UK>(session, realm,
Objects.equals(origEntity.getPersistenceState(), TRANSIENT) ? origEntity : registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return userSessionStore.getKeyConvertor().keyToString(entity.getId());
}
@Override
public void removeAuthenticatedClientSessions(Collection<String> removedClientUUIDS) {
removedClientUUIDS.forEach(entity::removeAuthenticatedClientSession);
public void removeAuthenticatedClientSessions(Collection<String> removedClientUKS) {
removedClientUKS.forEach(entity::removeAuthenticatedClientSession);
}
@Override
@ -111,7 +114,7 @@ public class MapUserSessionProvider implements UserSessionProvider {
};
}
private Function<MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientEntityToAdapterFunc(RealmModel realm,
private Function<MapAuthenticatedClientSessionEntity<CK>, AuthenticatedClientSessionModel> clientEntityToAdapterFunc(RealmModel realm,
ClientModel client,
UserSessionModel userSession) {
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
@ -121,7 +124,12 @@ public class MapUserSessionProvider implements UserSessionProvider {
clientSessionTx.delete(origEntity.getId());
return null;
} else {
return new MapAuthenticatedClientSessionAdapter(session, realm, client, userSession, registerEntityForChanges(origEntity)) {
return new MapAuthenticatedClientSessionAdapter<CK>(session, realm, client, userSession, registerEntityForChanges(origEntity)) {
@Override
public String getId() {
return clientSessionStore.getKeyConvertor().keyToString(entity.getId());
}
@Override
public void detachFromUserSession() {
this.userSession = null;
@ -140,15 +148,15 @@ public class MapUserSessionProvider implements UserSessionProvider {
};
}
private MapUserSessionEntity registerEntityForChanges(MapUserSessionEntity origEntity) {
MapUserSessionEntity res = userSessionTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
userSessionTx.updateIfChanged(origEntity.getId(), res, MapUserSessionEntity::isUpdated);
private MapUserSessionEntity<UK> registerEntityForChanges(MapUserSessionEntity<UK> origEntity) {
MapUserSessionEntity<UK> res = userSessionTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
userSessionTx.updateIfChanged(origEntity.getId(), res, MapUserSessionEntity<UK>::isUpdated);
return res;
}
private MapAuthenticatedClientSessionEntity registerEntityForChanges(MapAuthenticatedClientSessionEntity origEntity) {
MapAuthenticatedClientSessionEntity res = clientSessionTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
clientSessionTx.updateIfChanged(origEntity.getId(), res, MapAuthenticatedClientSessionEntity::isUpdated);
private MapAuthenticatedClientSessionEntity<CK> registerEntityForChanges(MapAuthenticatedClientSessionEntity<CK> origEntity) {
MapAuthenticatedClientSessionEntity<CK> res = clientSessionTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
clientSessionTx.updateIfChanged(origEntity.getId(), res, MapAuthenticatedClientSessionEntity<CK>::isUpdated);
return res;
}
@ -159,28 +167,28 @@ public class MapUserSessionProvider implements UserSessionProvider {
@Override
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
MapAuthenticatedClientSessionEntity entity =
new MapAuthenticatedClientSessionEntity(UUID.randomUUID(), userSession.getId(), realm.getId(), client.getId(), false);
MapAuthenticatedClientSessionEntity<CK> entity =
new MapAuthenticatedClientSessionEntity<>(clientSessionStore.getKeyConvertor().yieldNewUniqueKey(), userSession.getId(), realm.getId(), client.getId(), false);
setClientSessionExpiration(entity, realm, client);
LOG.tracef("createClientSession(%s, %s, %s)%s", realm, client, userSession, getShortStackTrace());
clientSessionTx.create(entity.getId(), entity);
MapUserSessionEntity userSessionEntity = getUserSessionById(UUID.fromString(userSession.getId()));
MapUserSessionEntity<UK> userSessionEntity = getUserSessionById(userSessionStore.getKeyConvertor().fromString(userSession.getId()));
if (userSessionEntity == null) {
throw new IllegalStateException("User session entity does not exist: " + userSession.getId());
}
userSessionEntity.addAuthenticatedClientSession(client.getId(), entity.getId());
userSessionEntity.addAuthenticatedClientSession(client.getId(), clientSessionStore.getKeyConvertor().keyToString(entity.getId()));
return clientEntityToAdapterFunc(realm, client, userSession).apply(entity);
}
@Override
public AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client,
UUID clientSessionId, boolean offline) {
String clientSessionId, boolean offline) {
LOG.tracef("getClientSession(%s, %s, %s, %s)%s", userSession, client,
clientSessionId, offline, getShortStackTrace());
@ -190,8 +198,9 @@ public class MapUserSessionProvider implements UserSessionProvider {
return null;
}
CK ck = clientSessionStore.getKeyConvertor().fromStringSafe(clientSessionId);
ModelCriteriaBuilder<AuthenticatedClientSessionModel> mcb = clientSessionStore.createCriteriaBuilder()
.compare(AuthenticatedClientSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, clientSessionId)
.compare(AuthenticatedClientSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, ck)
.compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, userSession.getId())
.compare(AuthenticatedClientSessionModel.SearchableFields.REALM_ID, ModelCriteriaBuilder.Operator.EQ, userSession.getRealm().getId())
.compare(AuthenticatedClientSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId())
@ -214,11 +223,11 @@ public class MapUserSessionProvider implements UserSessionProvider {
public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername,
String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId,
String brokerUserId, UserSessionModel.SessionPersistenceState persistenceState) {
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
final UK entityId = id == null ? userSessionStore.getKeyConvertor().yieldNewUniqueKey(): userSessionStore.getKeyConvertor().fromString(id);
LOG.tracef("createUserSession(%s, %s, %s, %s)%s", id, realm, loginUsername, persistenceState, getShortStackTrace());
MapUserSessionEntity entity = new MapUserSessionEntity(entityId, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, false);
MapUserSessionEntity<UK> entity = new MapUserSessionEntity<>(entityId, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, false);
entity.setPersistenceState(persistenceState);
setUserSessionExpiration(entity, realm);
@ -247,12 +256,12 @@ public class MapUserSessionProvider implements UserSessionProvider {
LOG.tracef("getUserSession(%s, %s)%s", realm, id, getShortStackTrace());
UUID uuid = toUUID(id);
UK uuid = userSessionStore.getKeyConvertor().fromStringSafe(id);
if (uuid == null) {
return null;
}
MapUserSessionEntity userSessionEntity = transientUserSessions.get(uuid);
MapUserSessionEntity<UK> userSessionEntity = transientUserSessions.get(uuid);
if (userSessionEntity != null) {
return userEntityToAdapterFunc(realm).apply(userSessionEntity);
}
@ -371,12 +380,13 @@ public class MapUserSessionProvider implements UserSessionProvider {
public void removeUserSession(RealmModel realm, UserSessionModel session) {
Objects.requireNonNull(session, "The provided user session can't be null!");
UK uk = userSessionStore.getKeyConvertor().fromString(session.getId());
ModelCriteriaBuilder<UserSessionModel> mcb = realmAndOfflineCriteriaBuilder(realm, false)
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(session.getId()));
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uk);
LOG.tracef("removeUserSession(%s, %s)%s", realm, session, getShortStackTrace());
userSessionTx.delete(UUID.randomUUID(), mcb);
userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override
@ -387,7 +397,7 @@ public class MapUserSessionProvider implements UserSessionProvider {
LOG.tracef("removeUserSessions(%s, %s)%s", realm, user, getShortStackTrace());
userSessionTx.delete(UUID.randomUUID(), mcb);
userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override
@ -406,7 +416,7 @@ public class MapUserSessionProvider implements UserSessionProvider {
LOG.tracef("removeUserSessions(%s)%s", realm, getShortStackTrace());
userSessionTx.delete(UUID.randomUUID(), mcb);
userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
}
@Override
@ -425,7 +435,7 @@ public class MapUserSessionProvider implements UserSessionProvider {
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
LOG.tracef("createOfflineUserSession(%s)%s", userSession, getShortStackTrace());
MapUserSessionEntity offlineUserSession = createUserSessionEntityInstance(userSession, true);
MapUserSessionEntity<UK> offlineUserSession = createUserSessionEntityInstance(userSession, true);
// set a reference for the offline user session to the original online user session
userSession.setNote(CORRESPONDING_SESSION_ID, offlineUserSession.getId().toString());
@ -458,11 +468,12 @@ public class MapUserSessionProvider implements UserSessionProvider {
ModelCriteriaBuilder<UserSessionModel> mcb;
if (userSession.isOffline()) {
userSessionTx.delete(UUID.fromString(userSession.getId()));
userSessionTx.delete(userSessionStore.getKeyConvertor().fromString(userSession.getId()));
} else if (userSession.getNote(CORRESPONDING_SESSION_ID) != null) {
UK uk = userSessionStore.getKeyConvertor().fromString(userSession.getNote(CORRESPONDING_SESSION_ID));
mcb = realmAndOfflineCriteriaBuilder(realm, true)
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(userSession.getNote(CORRESPONDING_SESSION_ID)));
userSessionTx.delete(UUID.randomUUID(), mcb);
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uk);
userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
userSession.removeNote(CORRESPONDING_SESSION_ID);
}
}
@ -472,13 +483,13 @@ public class MapUserSessionProvider implements UserSessionProvider {
UserSessionModel offlineUserSession) {
LOG.tracef("createOfflineClientSession(%s, %s)%s", clientSession, offlineUserSession, getShortStackTrace());
MapAuthenticatedClientSessionEntity clientSessionEntity = createAuthenticatedClientSessionInstance(clientSession, offlineUserSession, true);
MapAuthenticatedClientSessionEntity<CK> clientSessionEntity = createAuthenticatedClientSessionInstance(clientSession, offlineUserSession, true);
clientSessionEntity.setTimestamp(Time.currentTime());
setClientSessionExpiration(clientSessionEntity, clientSession.getRealm(), clientSession.getClient());
Optional<MapUserSessionEntity> userSessionEntity = getOfflineUserSessionEntityStream(clientSession.getRealm(), offlineUserSession.getId()).findFirst();
Optional<MapUserSessionEntity<UK>> userSessionEntity = getOfflineUserSessionEntityStream(clientSession.getRealm(), offlineUserSession.getId()).findFirst();
if (userSessionEntity.isPresent()) {
userSessionEntity.get().addAuthenticatedClientSession(clientSession.getClient().getId(), clientSessionEntity.getId());
userSessionEntity.get().addAuthenticatedClientSession(clientSession.getClient().getId(), clientSessionStore.getKeyConvertor().keyToString(clientSessionEntity.getId()));
}
clientSessionTx.create(clientSessionEntity.getId(), clientSessionEntity);
@ -556,17 +567,17 @@ public class MapUserSessionProvider implements UserSessionProvider {
persistentUserSessions.stream()
.map(pus -> {
MapUserSessionEntity userSessionEntity = new MapUserSessionEntity(UUID.randomUUID(), pus.getRealm(), pus.getUser(),
MapUserSessionEntity<UK> userSessionEntity = new MapUserSessionEntity<UK>(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), pus.getRealm(), pus.getUser(),
pus.getLoginUsername(), pus.getIpAddress(), pus.getAuthMethod(),
pus.isRememberMe(), pus.getBrokerSessionId(), pus.getBrokerUserId(), offline);
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : pus.getAuthenticatedClientSessions().entrySet()) {
MapAuthenticatedClientSessionEntity clientSession = createAuthenticatedClientSessionInstance(entry.getValue(), entry.getValue().getUserSession(), offline);
MapAuthenticatedClientSessionEntity<CK> clientSession = createAuthenticatedClientSessionInstance(entry.getValue(), entry.getValue().getUserSession(), offline);
// Update timestamp to same value as userSession. LastSessionRefresh of userSession from DB will have correct value
clientSession.setTimestamp(userSessionEntity.getLastSessionRefresh());
userSessionEntity.addAuthenticatedClientSession(entry.getKey(), clientSession.getId());
userSessionEntity.addAuthenticatedClientSession(entry.getKey(), clientSessionStore.getKeyConvertor().keyToString(clientSession.getId()));
clientSessionTx.create(clientSession.getId(), clientSession);
}
@ -581,8 +592,8 @@ public class MapUserSessionProvider implements UserSessionProvider {
}
private Stream<MapUserSessionEntity> getOfflineUserSessionEntityStream(RealmModel realm, String userSessionId) {
UUID uuid = toUUID(userSessionId);
private Stream<MapUserSessionEntity<UK>> getOfflineUserSessionEntityStream(RealmModel realm, String userSessionId) {
UK uuid = userSessionStore.getKeyConvertor().fromStringSafe(userSessionId);
if (uuid == null) {
return Stream.empty();
}
@ -593,7 +604,7 @@ public class MapUserSessionProvider implements UserSessionProvider {
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uuid);
// check if it's an offline user session
MapUserSessionEntity userSessionEntity = userSessionTx.getUpdatedNotRemoved(mcb).findFirst().orElse(null);
MapUserSessionEntity<UK> userSessionEntity = userSessionTx.getUpdatedNotRemoved(mcb).findFirst().orElse(null);
if (userSessionEntity != null) {
if (userSessionEntity.isOffline()) {
return Stream.of(userSessionEntity);
@ -608,8 +619,9 @@ public class MapUserSessionProvider implements UserSessionProvider {
// it's online user session so lookup offline user session by corresponding session id reference
String offlineUserSessionId = userSessionEntity.getNote(CORRESPONDING_SESSION_ID);
if (offlineUserSessionId != null) {
UK uk = userSessionStore.getKeyConvertor().fromStringSafe(offlineUserSessionId);
mcb = realmAndOfflineCriteriaBuilder(realm, true)
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(offlineUserSessionId));
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uk);
return userSessionTx.getUpdatedNotRemoved(mcb);
}
@ -622,18 +634,18 @@ public class MapUserSessionProvider implements UserSessionProvider {
.compare(UserSessionModel.SearchableFields.IS_OFFLINE, ModelCriteriaBuilder.Operator.EQ, offline);
}
private MapUserSessionEntity getUserSessionById(UUID id) {
MapUserSessionEntity userSessionEntity = transientUserSessions.get(id);
private MapUserSessionEntity<UK> getUserSessionById(UK id) {
MapUserSessionEntity<UK> userSessionEntity = transientUserSessions.get(id);
if (userSessionEntity == null) {
MapUserSessionEntity userSession = userSessionTx.read(id);
MapUserSessionEntity<UK> userSession = userSessionTx.read(id);
return userSession != null ? registerEntityForChanges(userSession) : null;
}
return userSessionEntity;
}
private MapUserSessionEntity createUserSessionEntityInstance(UserSessionModel userSession, boolean offline) {
MapUserSessionEntity entity = new MapUserSessionEntity(UUID.randomUUID(), userSession.getRealm().getId());
private MapUserSessionEntity<UK> createUserSessionEntityInstance(UserSessionModel userSession, boolean offline) {
MapUserSessionEntity<UK> entity = new MapUserSessionEntity<UK>(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), userSession.getRealm().getId());
entity.setAuthMethod(userSession.getAuthMethod());
entity.setBrokerSessionId(userSession.getBrokerSessionId());
@ -655,9 +667,9 @@ public class MapUserSessionProvider implements UserSessionProvider {
return entity;
}
private MapAuthenticatedClientSessionEntity createAuthenticatedClientSessionInstance(AuthenticatedClientSessionModel clientSession,
private MapAuthenticatedClientSessionEntity<CK> createAuthenticatedClientSessionInstance(AuthenticatedClientSessionModel clientSession,
UserSessionModel userSession, boolean offline) {
MapAuthenticatedClientSessionEntity entity = new MapAuthenticatedClientSessionEntity(UUID.randomUUID(),
MapAuthenticatedClientSessionEntity<CK> entity = new MapAuthenticatedClientSessionEntity<CK>(clientSessionStore.getKeyConvertor().yieldNewUniqueKey(),
userSession.getId(), clientSession.getRealm().getId(), clientSession.getClient().getId(), offline);
entity.setAction(clientSession.getAction());
@ -669,12 +681,4 @@ public class MapUserSessionProvider implements UserSessionProvider {
return entity;
}
private UUID toUUID(String id) {
try {
return UUID.fromString(id);
} catch (IllegalArgumentException ex) {
return null;
}
}
}

View file

@ -16,6 +16,8 @@
*/
package org.keycloak.models.map.userSession;
import org.keycloak.Config.Scope;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -28,40 +30,79 @@ import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.UUID;
import org.keycloak.models.map.storage.MapStorageSpi;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserSessionProviderFactory extends AbstractMapProviderFactory<UserSessionProvider>
implements UserSessionProviderFactory {
public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderFactory<UserSessionProvider>, UserSessionProviderFactory, ProviderEventListener {
private MapStorage<UUID, MapUserSessionEntity, UserSessionModel> userSessionStore;
private MapStorage<UUID, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore;
public static final String CONFIG_STORAGE_USER_SESSIONS = "storage-user-sessions";
public static final String CONFIG_STORAGE_CLIENT_SESSIONS = "storage-client-sessions";
public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID;
private Scope storageConfigScopeUserSessions;
private Scope storageConfigScopeClientSessions;
private Runnable onClose;
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void init(Scope config) {
storageConfigScopeUserSessions = config.scope(AbstractMapProviderFactory.CONFIG_STORAGE + "-user-sessions");
storageConfigScopeClientSessions = config.scope(AbstractMapProviderFactory.CONFIG_STORAGE + "-client-sessions");
}
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
userSessionStore = sp.getStorage("userSessions", UUID.class, MapUserSessionEntity.class, UserSessionModel.class);
clientSessionStore = sp.getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
factory.register(this);
onClose = () -> factory.unregister(this);
}
factory.register(event -> {
@Override
public void close() {
AmphibianProviderFactory.super.close();
onClose.run();
}
@Override
public void loadPersistentSessions(KeycloakSessionFactory sessionFactory, int maxErrors, int sessionsPerSegment) {
}
@Override
public MapUserSessionProvider<UK, CK> create(KeycloakSession session) {
MapStorageProviderFactory storageProviderFactoryUs = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
MapStorageProvider.class, storageConfigScopeUserSessions, MapStorageSpi.NAME);
final MapStorageProvider factoryUs = storageProviderFactoryUs.create(session);
MapStorage userSessionStore = factoryUs.getStorage(MapUserSessionEntity.class, UserSessionModel.class);
MapStorageProviderFactory storageProviderFactoryCs = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
MapStorageProvider.class, storageConfigScopeClientSessions, MapStorageSpi.NAME);
final MapStorageProvider factoryCs = storageProviderFactoryCs.create(session);
MapStorage clientSessionStore = factoryCs.getStorage(MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
return new MapUserSessionProvider<>(session, userSessionStore, clientSessionStore);
}
@Override
public String getHelpText() {
return "User session provider";
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof UserModel.UserRemovedEvent) {
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
MapUserSessionProvider provider = MapUserSessionProviderFactory.this.create(userRemovedEvent.getKeycloakSession());
provider.removeUserSessions(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
}
});
}
@Override
public void loadPersistentSessions(KeycloakSessionFactory sessionFactory, int maxErrors, int sessionsPerSegment) {
}
@Override
public MapUserSessionProvider create(KeycloakSession session) {
return new MapUserSessionProvider(session, userSessionStore, clientSessionStore);
}
}

View file

@ -26,7 +26,7 @@ import org.keycloak.protocol.oidc.OIDCConfigAttributes;
*/
public class SessionExpiration {
public static void setClientSessionExpiration(MapAuthenticatedClientSessionEntity entity, RealmModel realm, ClientModel client) {
public static <K> void setClientSessionExpiration(MapAuthenticatedClientSessionEntity<K> entity, RealmModel realm, ClientModel client) {
if (entity.isOffline()) {
long sessionExpires = entity.getTimestamp() + realm.getOfflineSessionIdleTimeout();
if (realm.isOfflineSessionMaxLifespanEnabled()) {
@ -99,7 +99,7 @@ public class SessionExpiration {
}
}
public static void setUserSessionExpiration(MapUserSessionEntity entity, RealmModel realm) {
public static <K> void setUserSessionExpiration(MapUserSessionEntity<K> entity, RealmModel realm) {
if (entity.isOffline()) {
long sessionExpires = entity.getLastSessionRefresh() + realm.getOfflineSessionIdleTimeout();
if (realm.isOfflineSessionMaxLifespanEnabled()) {

View file

@ -28,11 +28,11 @@ import java.util.stream.Collectors;
public class AbstractUserEntityCredentialsOrderTest {
private AbstractUserEntity<Integer> user;
private MapUserEntity<Integer> user;
@Before
public void init() {
user = new AbstractUserEntity<Integer>(1, "realmId") {};
user = new MapUserEntity<Integer>(1, "realmId") {};
for (int i = 1; i <= 5; i++) {
UserCredentialEntity credentialModel = new UserCredentialEntity();

View file

@ -26,6 +26,9 @@ import org.keycloak.provider.Spi;
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class StoreFactorySpi implements Spi {
public static final String NAME = "authorizationPersister";
@Override
public boolean isInternal() {
return true;
@ -33,7 +36,7 @@ public class StoreFactorySpi implements Spi {
@Override
public String getName() {
return "authorizationPersister";
return NAME;
}
@Override

View file

@ -23,6 +23,8 @@ import org.keycloak.provider.Spi;
public class ServerInfoSpi implements Spi {
public static final String NAME = "serverInfo";
@Override
public boolean isInternal() {
return true;
@ -30,7 +32,7 @@ public class ServerInfoSpi implements Spi {
@Override
public String getName() {
return "serverInfo";
return NAME;
}
@Override

View file

@ -31,6 +31,7 @@ public interface UserLoginFailureModel {
public static final SearchableModelField<UserLoginFailureModel> USER_ID = new SearchableModelField<>("userId", String.class);
}
String getId();
String getUserId();
int getFailedLoginNotBefore();
void setFailedLoginNotBefore(int notBefore);

View file

@ -40,7 +40,14 @@ public interface UserSessionProvider extends Provider {
KeycloakSession getKeycloakSession();
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline);
/**
* @deprecated Use {@link #getClientSession(UserSessionModel, ClientModel, String, boolean)} instead.
*/
default AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline) {
return getClientSession(userSession, client, clientSessionId == null ? null : clientSessionId.toString(), offline);
}
AuthenticatedClientSessionModel getClientSession(UserSessionModel userSession, ClientModel client, String clientSessionId, boolean offline);
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);

View file

@ -59,10 +59,12 @@ public class DefaultComponentFactoryProviderFactory implements ComponentFactoryP
private KeycloakSessionFactory factory;
private boolean componentCachingAvailable;
private boolean componentCachingEnabled;
private Boolean componentCachingForced;
@Override
public void init(Scope config) {
this.componentCachingEnabled = config.getBoolean("cachingEnabled", true);
this.componentCachingForced = config.getBoolean("cachingForced", false);
}
@Override
@ -72,7 +74,12 @@ public class DefaultComponentFactoryProviderFactory implements ComponentFactoryP
if (! componentCachingEnabled) {
LOG.warn("Caching of components disabled by the configuration which may have performance impact.");
} else if (! componentCachingAvailable) {
LOG.warn("No system-wide ClusterProviderFactory found. Cannot send messages across cluster, thus disabling caching of components.");
if (Objects.equals(componentCachingForced, Boolean.TRUE)) {
LOG.warn("Component caching forced even though no system-wide ClusterProviderFactory found. This would be only reliable in single-node deployment.");
this.componentCachingAvailable = true;
} else {
LOG.warn("No system-wide ClusterProviderFactory found. Cannot send messages across cluster, thus disabling caching of components. Consider setting cachingForced option in single-node deployment.");
}
}
}
@ -120,9 +127,13 @@ public class DefaultComponentFactoryProviderFactory implements ComponentFactoryP
Scope scope = Config.scope(factory.getSpi(clazz).getName(), provider);
ComponentModelScope configScope = new ComponentModelScope(scope, cm);
return this.componentCachingAvailable
? componentsMap.get().computeIfAbsent(componentId, cId -> initializeFactory(clazz, realmId, componentId, newFactory, configScope))
: initializeFactory(clazz, realmId, componentId, newFactory, configScope);
ProviderFactory<T> providerFactory;
if (this.componentCachingAvailable) {
providerFactory = componentsMap.get().computeIfAbsent(componentId, cId -> initializeFactory(clazz, realmId, componentId, newFactory, configScope));
} else {
providerFactory = initializeFactory(clazz, realmId, componentId, newFactory, configScope);
}
return providerFactory;
}
@SuppressWarnings("unchecked")

View file

@ -82,7 +82,9 @@
"mapStorage": {
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
"concurrenthashmap": {
"dir": "${project.build.directory:target}"
"dir": "${project.build.directory:target}",
"keyType.realms": "string",
"keyType.authz-resource-servers": "string"
}
},

Some files were not shown because too many files have changed in this diff Show more