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> {
public static final Comparator<MapResourceEntity<?>> COMPARE_BY_NAME = Comparator.comparing(MapResourceEntity::getName);
protected MapResourceEntity() {
super();
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

@ -1,13 +1,13 @@
/*
* 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.
@ -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

@ -1,13 +1,13 @@
/*
* 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.
@ -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

@ -1,13 +1,13 @@
/*
* 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.
@ -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(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) {
RealmModel.RealmRemovedEvent realmRemovedEvent = (RealmModel.RealmRemovedEvent) event;
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(realmRemovedEvent.getKeycloakSession());
provider.removeAllUserLoginFailures(realmRemovedEvent.getRealm());
}
});
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public MapUserLoginFailureProvider create(KeycloakSession session) {
return new MapUserLoginFailureProvider(session, userLoginFailureStore);
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());
} else if (event instanceof RealmModel.RealmRemovedEvent) {
RealmModel.RealmRemovedEvent realmRemovedEvent = (RealmModel.RealmRemovedEvent) event;
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(realmRemovedEvent.getKeycloakSession());
provider.removeAllUserLoginFailures(realmRemovedEvent.getRealm());
}
}
}

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

@ -1,13 +1,13 @@
/*
* 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.
@ -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) {
return read(key, map::read);
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) {
final String dir = config.get("dir");
if (dir == null || dir.trim().isEmpty()) {
LOG.warn("No directory set, created objects will not survive server restart");
this.storageDirectory = null;
if (config instanceof ComponentModelScope) {
this.suffix = "-" + ((ComponentModelScope) config).getComponentId();
} else {
File f = new File(dir);
try {
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);
Files.createDirectories(f.toPath());
if (f.exists()) {
this.storageDirectory = f;
@ -72,13 +137,21 @@ 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;
}
} 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
public void postInit(KeycloakSessionFactory factory) {
}
@ -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

@ -1,13 +1,13 @@
/*
* 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.
@ -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> {
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;
public static final Comparator<MapUserEntity> COMPARE_BY_USERNAME = Comparator.comparing(MapUserEntity::getUsername, String.CASE_INSENSITIVE_ORDER);
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,
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId,
boolean offline) {
super(id, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, offline);
public MapUserSessionEntity(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, 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,12 +468,13 @@ 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) {
mcb = realmAndOfflineCriteriaBuilder(realm, true)
.compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, UUID.fromString(userSession.getNote(CORRESPONDING_SESSION_ID)));
userSessionTx.delete(UUID.randomUUID(), mcb);
userSession.removeNote(CORRESPONDING_SESSION_ID);
UK uk = userSessionStore.getKeyConvertor().fromString(userSession.getNote(CORRESPONDING_SESSION_ID));
mcb = realmAndOfflineCriteriaBuilder(realm, true)
.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 -> {
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 close() {
AmphibianProviderFactory.super.close();
onClose.run();
}
@Override
public void loadPersistentSessions(KeycloakSessionFactory sessionFactory, int maxErrors, int sessionsPerSegment) {
}
@Override
public MapUserSessionProvider create(KeycloakSession session) {
return new MapUserSessionProvider(session, userSessionStore, clientSessionStore);
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());
}
}
}

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