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=userSessions:add(default-provider=map)
/subsystem=keycloak-server/spi=mapStorage:add(default-provider=concurrenthashmap) /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 quit

View file

@ -276,6 +276,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
.map(entity -> this.wrap(realm, entity, offline)); .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 @Override
public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline) { public AuthenticatedClientSessionAdapter getClientSession(UserSessionModel userSession, ClientModel client, UUID clientSessionId, boolean offline) {
AuthenticatedClientSessionEntity entity = getClientSessionEntity(clientSessionId, offline); AuthenticatedClientSessionEntity entity = getClientSessionEntity(clientSessionId, offline);

View file

@ -136,4 +136,9 @@ public class UserLoginFailureAdapter implements UserLoginFailureModel {
provider.getLoginFailuresTx().addTask(key, task); 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 KeycloakSession session;
private final MapRootAuthenticationSessionAdapter parent; private final MapRootAuthenticationSessionAdapter parent;
private final String tabId; private final String tabId;
private MapAuthenticationSessionEntity entity; private final MapAuthenticationSessionEntity entity;
public MapAuthenticationSessionAdapter(KeycloakSession session, MapRootAuthenticationSessionAdapter parent, public MapAuthenticationSessionAdapter(KeycloakSession session, MapRootAuthenticationSessionAdapter parent,
String tabId, MapAuthenticationSessionEntity entity) { String tabId, MapAuthenticationSessionEntity entity) {

View file

@ -81,6 +81,6 @@ public class MapAuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent
@Override @Override
public String toString() { public String toString() {
return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, tabId=%s, clientUUID=%s, authNotesFragment=%s ]", 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> * @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); super(session, realm, entity);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public RealmModel getRealm() { public RealmModel getRealm() {
return session.realms().getRealm(entity.getRealmId()); return session.realms().getRealm(entity.getRealmId());
@ -102,9 +97,7 @@ public class MapRootAuthenticationSessionAdapter extends AbstractRootAuthenticat
public void removeAuthenticationSessionByTabId(String tabId) { public void removeAuthenticationSessionByTabId(String tabId) {
if (entity.removeAuthenticationSession(tabId) != null) { if (entity.removeAuthenticationSession(tabId) != null) {
if (entity.getAuthenticationSessions().isEmpty()) { if (entity.getAuthenticationSessions().isEmpty()) {
MapRootAuthenticationSessionProvider authenticationSessionProvider = session.authenticationSessions().removeRootAuthenticationSession(realm, this);
(MapRootAuthenticationSessionProvider) session.authenticationSessions();
authenticationSessionProvider.tx.delete(entity.getId());
} else { } else {
entity.setTimestamp(Time.currentTime()); entity.setTimestamp(Time.currentTime());
} }

View file

@ -16,18 +16,89 @@
*/ */
package org.keycloak.models.map.authSession; 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> * @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() { protected MapRootAuthenticationSessionEntity() {
super(); this.id = null;
this.realmId = null;
} }
public MapRootAuthenticationSessionEntity(UUID id, String realmId) { public MapRootAuthenticationSessionEntity(K id, String realmId) {
super(id, 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 org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; 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> * @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 static final Logger LOG = Logger.getLogger(MapRootAuthenticationSessionProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
protected final MapKeycloakTransaction<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> tx; protected final MapKeycloakTransaction<K, MapRootAuthenticationSessionEntity<K>, RootAuthenticationSessionModel> tx;
private final MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> sessionStore; 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"; 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.session = session;
this.sessionStore = sessionStore; this.sessionStore = sessionStore;
this.tx = sessionStore.createTransaction(session); this.tx = sessionStore.createTransaction(session);
@ -63,21 +61,26 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
session.getTransactionManager().enlistAfterCompletion(tx); 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 // 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) { private MapRootAuthenticationSessionEntity<K> registerEntityForChanges(MapRootAuthenticationSessionEntity<K> origEntity) {
MapRootAuthenticationSessionEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity)); MapRootAuthenticationSessionEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapRootAuthenticationSessionEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapRootAuthenticationSessionEntity<K>::isUpdated);
return res; return res;
} }
private Predicate<MapRootAuthenticationSessionEntity> entityRealmFilter(String realmId) { private Predicate<MapRootAuthenticationSessionEntity<K>> entityRealmFilter(String realmId) {
if (realmId == null) { if (realmId == null) {
return MapRootAuthenticationSessionProvider.ALWAYS_FALSE; return c -> false;
} }
return entity -> Objects.equals(realmId, entity.getRealmId()); return entity -> Objects.equals(realmId, entity.getRealmId());
} }
@ -92,12 +95,12 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
public RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm, String id) { public RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm, String id) {
Objects.requireNonNull(realm, "The provided realm can't be null!"); 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()); LOG.tracef("createRootAuthenticationSession(%s)%s", realm.getName(), getShortStackTrace());
// create map authentication session entity // 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.setRealmId(realm.getId());
entity.setTimestamp(Time.currentTime()); entity.setTimestamp(Time.currentTime());
@ -119,7 +122,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
LOG.tracef("getRootAuthenticationSession(%s, %s)%s", realm.getName(), authenticationSessionId, getShortStackTrace()); 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)) return (entity == null || !entityRealmFilter(realm.getId()).test(entity))
? null ? null
: entityToAdapterFunc(realm).apply(entity); : entityToAdapterFunc(realm).apply(entity);
@ -128,7 +131,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
@Override @Override
public void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession) { public void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession) {
Objects.requireNonNull(authenticationSession, "The provided root authentication session can't be null!"); 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 @Override
@ -147,7 +150,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.TIMESTAMP, Operator.LT, expired); .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()); 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; package org.keycloak.models.map.authSession;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory; 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.AuthenticationSessionProvider;
import org.keycloak.sessions.AuthenticationSessionProviderFactory; import org.keycloak.sessions.AuthenticationSessionProviderFactory;
import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.UUID;
/** /**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a> * @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 { implements AuthenticationSessionProviderFactory {
private MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> store; public MapRootAuthenticationSessionProviderFactory() {
super(MapRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class);
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("sessions", UUID.class, MapRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class);
} }
@Override @Override
public AuthenticationSessionProvider create(KeycloakSession session) { 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; package org.keycloak.models.map.authorization;
import org.keycloak.authorization.AuthorizationProvider; 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.PermissionTicketStore;
import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceServerStore; 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.ScopeStore;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession; 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 org.keycloak.models.map.storage.MapStorage;
import java.util.UUID;
/** /**
* @author mhajas * @author mhajas
@ -51,7 +40,7 @@ public class MapAuthorizationStore implements StoreFactory {
private final PermissionTicketStore permissionTicketStore; private final PermissionTicketStore permissionTicketStore;
private boolean readOnly; 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.permissionTicketStore = new MapPermissionTicketStore(session, permissionTicketStore, provider);
this.policyStore = new MapPolicyStore(session, policyStore, provider); this.policyStore = new MapPolicyStore(session, policyStore, provider);
this.resourceServerStore = new MapResourceServerStore(session, resourceServerStore, 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.model.Scope;
import org.keycloak.authorization.store.AuthorizationStoreFactory; import org.keycloak.authorization.store.AuthorizationStoreFactory;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.KeycloakSession; 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.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity; import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity; import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity; 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.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider; import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory; 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 * @author mhajas
*/ */
public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory { public class MapAuthorizationStoreFactory<K> implements AmphibianProviderFactory<StoreFactory>, AuthorizationStoreFactory {
private MapStorage<UUID, MapPermissionTicketEntity, PermissionTicket> permissionTicketStore; public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID;
private MapStorage<UUID, MapPolicyEntity, Policy> policyStore;
private MapStorage<String, MapResourceServerEntity, ResourceServer> resourceServerStore; private Config.Scope storageConfigScope;
private MapStorage<UUID, MapResourceEntity, Resource> resourceStore;
private MapStorage<UUID, MapScopeEntity, Scope> scopeStore;
@Override @Override
public StoreFactory create(KeycloakSession session) { 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); 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, return new MapAuthorizationStore(session,
permissionTicketStore, permissionTicketStore,
policyStore, policyStore,
@ -63,21 +80,8 @@ public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory {
} }
@Override @Override
public void init(Config.Scope config) { public void init(org.keycloak.Config.Scope config) {
this.storageConfigScope = config.scope("storage");
}
@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);
} }
@Override @Override
@ -87,6 +91,11 @@ public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory {
@Override @Override
public String getId() { 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.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.map.authorization.adapter.MapPermissionTicketAdapter; 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.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.common.Serialization; import org.keycloak.models.map.common.Serialization;
import org.keycloak.models.map.storage.MapKeycloakTransaction; 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 org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; 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.distinctByKey;
import static org.keycloak.utils.StreamsUtil.paginatedStream; 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 static final Logger LOG = Logger.getLogger(MapPermissionTicketStore.class);
private final AuthorizationProvider authorizationProvider; private final AuthorizationProvider authorizationProvider;
final MapKeycloakTransaction<UUID, MapPermissionTicketEntity, PermissionTicket> tx; final MapKeycloakTransaction<K, MapPermissionTicketEntity<K>, PermissionTicket> tx;
private final MapStorage<UUID, MapPermissionTicketEntity, PermissionTicket> permissionTicketStore; 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.authorizationProvider = provider;
this.permissionTicketStore = permissionTicketStore; this.permissionTicketStore = permissionTicketStore;
this.tx = permissionTicketStore.createTransaction(session); this.tx = permissionTicketStore.createTransaction(session);
session.getTransactionManager().enlist(tx); session.getTransactionManager().enlist(tx);
} }
private MapPermissionTicketEntity registerEntityForChanges(MapPermissionTicketEntity origEntity) { private MapPermissionTicketEntity<K> registerEntityForChanges(MapPermissionTicketEntity<K> origEntity) {
final MapPermissionTicketEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity)); final MapPermissionTicketEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapPermissionTicketEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapPermissionTicketEntity<K>::isUpdated);
return res; return res;
} }
private PermissionTicket entityToAdapter(MapPermissionTicketEntity origEntity) { private PermissionTicket entityToAdapter(MapPermissionTicketEntity<K> origEntity) {
if (origEntity == null) return null; if (origEntity == null) return null;
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller // 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) { private ModelCriteriaBuilder<PermissionTicket> forResourceServer(String resourceServerId) {
@ -116,13 +120,14 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
+ ", Resource: " + resourceId + ", owner: " + owner + ", scopeId: " + scopeId + " already exists."); + ", Resource: " + resourceId + ", owner: " + owner + ", scopeId: " + scopeId + " already exists.");
} }
MapPermissionTicketEntity entity = new MapPermissionTicketEntity(UUID.randomUUID()); final K newId = permissionTicketStore.getKeyConvertor().yieldNewUniqueKey();
entity.setResourceId(UUID.fromString(resourceId)); MapPermissionTicketEntity<K> entity = new MapPermissionTicketEntity<>(newId);
entity.setResourceId(resourceId);
entity.setRequester(requester); entity.setRequester(requester);
entity.setCreatedTimestamp(System.currentTimeMillis()); entity.setCreatedTimestamp(System.currentTimeMillis());
if (scopeId != null) { if (scopeId != null) {
entity.setScopeId(UUID.fromString(scopeId)); entity.setScopeId(scopeId);
} }
entity.setOwner(owner); entity.setOwner(owner);
@ -136,7 +141,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
@Override @Override
public void delete(String id) { public void delete(String id) {
LOG.tracef("delete(%s)%s", id, getShortStackTrace()); LOG.tracef("delete(%s)%s", id, getShortStackTrace());
tx.delete(UUID.fromString(id)); tx.delete(permissionTicketStore.getKeyConvertor().fromString(id));
} }
@Override @Override
@ -204,7 +209,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
if (r == null || r.isEmpty()) { if (r == null || r.isEmpty()) {
return Collections.emptyList(); 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( mcb = mcb.and(
@ -213,8 +218,9 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
.toArray(ModelCriteriaBuilder[]::new) .toArray(ModelCriteriaBuilder[]::new)
); );
Comparator<? super MapPermissionTicketEntity<K>> c = Comparator.comparing(MapPermissionTicketEntity::getId);
return paginatedStream(tx.getUpdatedNotRemoved(mcb) return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.sorted(MapPermissionTicketEntity.COMPARE_BY_ID), firstResult, maxResult) .sorted(c), firstResult, maxResult)
.map(this::entityToAdapter) .map(this::entityToAdapter)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -278,14 +284,14 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
.compare(SearchableFields.REQUESTER, Operator.EQ, requester) .compare(SearchableFields.REQUESTER, Operator.EQ, requester)
.compare(SearchableFields.GRANTED_TIMESTAMP, Operator.EXISTS); .compare(SearchableFields.GRANTED_TIMESTAMP, Operator.EXISTS);
Function<MapPermissionTicketEntity, Resource> ticketResourceMapper; Function<MapPermissionTicketEntity<K>, Resource> ticketResourceMapper;
ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
if (name != null) { if (name != null) {
ticketResourceMapper = ticket -> { ticketResourceMapper = ticket -> {
Map<Resource.FilterOption, String[]> filterOptionMap = new EnumMap<>(Resource.FilterOption.class); 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}); filterOptionMap.put(Resource.FilterOption.NAME, new String[] {name});
List<Resource> resource = resourceStore.findByResourceServer(filterOptionMap, ticket.getResourceServerId(), -1, 1); List<Resource> resource = resourceStore.findByResourceServer(filterOptionMap, ticket.getResourceServerId(), -1, 1);
@ -294,11 +300,11 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
}; };
} else { } else {
ticketResourceMapper = ticket -> resourceStore ticketResourceMapper = ticket -> resourceStore
.findById(ticket.getResourceId().toString(), ticket.getResourceServerId()); .findById(ticket.getResourceId(), ticket.getResourceServerId());
} }
return paginatedStream(tx.getUpdatedNotRemoved(mcb) return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.filter(distinctByKey(AbstractPermissionTicketEntity::getResourceId)) .filter(distinctByKey(MapPermissionTicketEntity::getResourceId))
.sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID) .sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID)
.map(ticketResourceMapper) .map(ticketResourceMapper)
.filter(Objects::nonNull), first, max) .filter(Objects::nonNull), first, max)
@ -311,10 +317,10 @@ public class MapPermissionTicketStore implements PermissionTicketStore {
.compare(SearchableFields.OWNER, Operator.EQ, owner); .compare(SearchableFields.OWNER, Operator.EQ, owner);
return paginatedStream(tx.getUpdatedNotRemoved(mcb) return paginatedStream(tx.getUpdatedNotRemoved(mcb)
.filter(distinctByKey(AbstractPermissionTicketEntity::getResourceId)) .filter(distinctByKey(MapPermissionTicketEntity::getResourceId))
.sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID), first, max) .sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID), first, max)
.map(ticket -> authorizationProvider.getStoreFactory().getResourceStore() .map(ticket -> authorizationProvider.getStoreFactory().getResourceStore()
.findById(ticket.getResourceId().toString(), ticket.getResourceServerId())) .findById(ticket.getResourceId(), ticket.getResourceServerId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@ import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects; 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; 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.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import java.util.UUID;
import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy; 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); super(entity, storeFactory);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getOwner() { public String getOwner() {
return entity.getOwner(); return entity.getOwner();
@ -52,13 +46,13 @@ public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel<Ma
@Override @Override
public Resource getResource() { public Resource getResource() {
return storeFactory.getResourceStore().findById(entity.getResourceId().toString(), entity.getResourceServerId()); return storeFactory.getResourceStore().findById(entity.getResourceId(), entity.getResourceServerId());
} }
@Override @Override
public Scope getScope() { public Scope getScope() {
if (entity.getScopeId() == null) return null; if (entity.getScopeId() == null) return null;
return storeFactory.getScopeStore().findById(entity.getScopeId().toString(), entity.getResourceServerId()); return storeFactory.getScopeStore().findById(entity.getScopeId(), entity.getResourceServerId());
} }
@Override @Override
@ -90,13 +84,13 @@ public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel<Ma
@Override @Override
public Policy getPolicy() { public Policy getPolicy() {
if (entity.getPolicyId() == null) return null; if (entity.getPolicyId() == null) return null;
return storeFactory.getPolicyStore().findById(entity.getPolicyId().toString(), entity.getResourceServerId()); return storeFactory.getPolicyStore().findById(entity.getPolicyId(), entity.getResourceServerId());
} }
@Override @Override
public void setPolicy(Policy policy) { public void setPolicy(Policy policy) {
if (policy != null) { 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.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; 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); super(entity, storeFactory);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getType() { public String getType() {
return entity.getType(); return entity.getType();
@ -123,7 +117,7 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public Set<Policy> getAssociatedPolicies() { public Set<Policy> getAssociatedPolicies() {
String resourceServerId = entity.getResourceServerId(); String resourceServerId = entity.getResourceServerId();
return entity.getAssociatedPoliciesIds().stream() return entity.getAssociatedPoliciesIds().stream()
.map(policyId -> storeFactory.getPolicyStore().findById(policyId.toString(), resourceServerId)) .map(policyId -> storeFactory.getPolicyStore().findById(policyId, resourceServerId))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@ -131,7 +125,7 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public Set<Resource> getResources() { public Set<Resource> getResources() {
String resourceServerId = entity.getResourceServerId(); String resourceServerId = entity.getResourceServerId();
return entity.getResourceIds().stream() return entity.getResourceIds().stream()
.map(resourceId -> storeFactory.getResourceStore().findById(resourceId.toString(), resourceServerId)) .map(resourceId -> storeFactory.getResourceStore().findById(resourceId, resourceServerId))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@ -139,7 +133,7 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
public Set<Scope> getScopes() { public Set<Scope> getScopes() {
String resourceServerId = entity.getResourceServerId(); String resourceServerId = entity.getResourceServerId();
return entity.getScopeIds().stream() return entity.getScopeIds().stream()
.map(scopeId -> storeFactory.getScopeStore().findById(scopeId.toString(), resourceServerId)) .map(scopeId -> storeFactory.getScopeStore().findById(scopeId, resourceServerId))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@ -157,37 +151,37 @@ public class MapPolicyAdapter extends AbstractPolicyModel<MapPolicyEntity> {
@Override @Override
public void addScope(Scope scope) { public void addScope(Scope scope) {
throwExceptionIfReadonly(); throwExceptionIfReadonly();
entity.addScope(UUID.fromString(scope.getId())); entity.addScope(scope.getId());
} }
@Override @Override
public void removeScope(Scope scope) { public void removeScope(Scope scope) {
throwExceptionIfReadonly(); throwExceptionIfReadonly();
entity.removeScope(UUID.fromString(scope.getId())); entity.removeScope(scope.getId());
} }
@Override @Override
public void addAssociatedPolicy(Policy associatedPolicy) { public void addAssociatedPolicy(Policy associatedPolicy) {
throwExceptionIfReadonly(); throwExceptionIfReadonly();
entity.addAssociatedPolicy(UUID.fromString(associatedPolicy.getId())); entity.addAssociatedPolicy(associatedPolicy.getId());
} }
@Override @Override
public void removeAssociatedPolicy(Policy associatedPolicy) { public void removeAssociatedPolicy(Policy associatedPolicy) {
throwExceptionIfReadonly(); throwExceptionIfReadonly();
entity.removeAssociatedPolicy(UUID.fromString(associatedPolicy.getId())); entity.removeAssociatedPolicy(associatedPolicy.getId());
} }
@Override @Override
public void addResource(Resource resource) { public void addResource(Resource resource) {
throwExceptionIfReadonly(); throwExceptionIfReadonly();
entity.addResource(UUID.fromString(resource.getId())); entity.addResource(resource.getId());
} }
@Override @Override
public void removeResource(Resource resource) { public void removeResource(Resource resource) {
throwExceptionIfReadonly(); throwExceptionIfReadonly();
entity.removeResource(UUID.fromString(resource.getId())); entity.removeResource(resource.getId());
} }
@Override @Override

View file

@ -19,27 +19,21 @@ package org.keycloak.models.map.authorization.adapter;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory; 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.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; 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); super(entity, storeFactory);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getName() { public String getName() {
return entity.getName(); return entity.getName();
@ -88,7 +82,7 @@ public class MapResourceAdapter extends AbstractResourceModel<MapResourceEntity>
public List<Scope> getScopes() { public List<Scope> getScopes() {
return entity.getScopeIds().stream() return entity.getScopeIds().stream()
.map(id -> storeFactory .map(id -> storeFactory
.getScopeStore().findById(id.toString(), entity.getResourceServerId())) .getScopeStore().findById(id, entity.getResourceServerId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -127,7 +121,7 @@ public class MapResourceAdapter extends AbstractResourceModel<MapResourceEntity>
@Override @Override
public void updateScopes(Set<Scope> scopes) { public void updateScopes(Set<Scope> scopes) {
throwExceptionIfReadonly(); 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 @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.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; 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); super(entity, storeFactory);
} }
@Override
public String getId() {
return entity.getId();
}
@Override @Override
public boolean isAllowRemoteResourceManagement() { public boolean isAllowRemoteResourceManagement() {
return entity.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.authorization.store.StoreFactory;
import org.keycloak.models.map.authorization.entity.MapScopeEntity; 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); super(entity, storeFactory);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getName() { public String getName() {
return entity.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; package org.keycloak.models.map.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Comparator; 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<MapPermissionTicketEntity<?>> COMPARE_BY_RESOURCE_ID = Comparator.comparing(MapPermissionTicketEntity::getResourceId);
public static final Comparator<AbstractPermissionTicketEntity<UUID>> COMPARE_BY_RESOURCE_ID = Comparator.comparing(AbstractPermissionTicketEntity::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() { public MapPermissionTicketEntity(K id) {
super(); this.id = id;
} }
public MapPermissionTicketEntity(UUID id) { public MapPermissionTicketEntity() {
super(id); 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 @Override

View file

@ -17,15 +17,176 @@
package org.keycloak.models.map.authorization.entity; 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> { import java.util.Comparator;
protected MapPolicyEntity() { import java.util.HashMap;
super(); 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) { public MapPolicyEntity() {
super(id); 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 @Override

View file

@ -17,18 +17,167 @@
package org.keycloak.models.map.authorization.entity; package org.keycloak.models.map.authorization.entity;
import org.keycloak.models.map.common.AbstractEntity;
import java.util.Comparator; 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 class MapResourceEntity<K> implements AbstractEntity<K> {
public static final Comparator<AbstractResourceEntity<UUID>> COMPARE_BY_ID = Comparator.comparing(AbstractResourceEntity::getId);
public static final Comparator<MapResourceEntity<?>> COMPARE_BY_NAME = Comparator.comparing(MapResourceEntity::getName);
protected MapResourceEntity() { private final K id;
super(); 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) { public MapResourceEntity() {
super(id); 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 @Override

View file

@ -17,13 +17,64 @@
package org.keycloak.models.map.authorization.entity; package org.keycloak.models.map.authorization.entity;
public class MapResourceServerEntity extends AbstractResourceServerEntity<String> { import org.keycloak.models.map.common.AbstractEntity;
protected MapResourceServerEntity() { import org.keycloak.representations.idm.authorization.DecisionStrategy;
super(); 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) { public MapResourceServerEntity() {
super(id); 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 @Override

View file

@ -17,15 +17,71 @@
package org.keycloak.models.map.authorization.entity; package org.keycloak.models.map.authorization.entity;
import java.util.UUID; import org.keycloak.models.map.common.AbstractEntity;
public class MapScopeEntity extends AbstractScopeEntity<UUID> { import java.util.Objects;
protected MapScopeEntity() {
super(); 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) { public MapScopeEntity() {
super(id); 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 @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 * @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); super(session, realm, entity);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getClientId() { public String getClientId() {
return entity.getClientId(); return entity.getClientId();

View file

@ -1,13 +1,13 @@
/* /*
* Copyright 2020 Red Hat, Inc. and/or its affiliates * Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,20 +16,467 @@
*/ */
package org.keycloak.models.map.client; 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 * @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() { protected MapClientEntity() {
super(); this.id = null;
this.realmId = null;
} }
public MapClientEntity(UUID id, String realmId) { public MapClientEntity(K id, String realmId) {
super(id, 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.storage.MapKeycloakTransaction;
import org.keycloak.models.map.common.Serialization; import org.keycloak.models.map.common.Serialization;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Function; import java.util.function.Function;
@ -50,18 +48,17 @@ import org.keycloak.models.ClientScopeModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import static org.keycloak.utils.StreamsUtil.paginatedStream; 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 Logger LOG = Logger.getLogger(MapClientProvider.class);
private static final Predicate<MapClientEntity> ALWAYS_FALSE = c -> { return false; };
private final KeycloakSession session; private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapClientEntity, ClientModel> tx; final MapKeycloakTransaction<K, MapClientEntity<K>, ClientModel> tx;
private final MapStorage<UUID, MapClientEntity, ClientModel> clientStore; private final MapStorage<K, MapClientEntity<K>, ClientModel> clientStore;
private final ConcurrentMap<UUID, ConcurrentMap<String, Integer>> clientRegisteredNodesStore; private final ConcurrentMap<K, ConcurrentMap<String, Integer>> clientRegisteredNodesStore;
private static final Comparator<MapClientEntity> COMPARE_BY_CLIENT_ID = Comparator.comparing(MapClientEntity::getClientId); 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.session = session;
this.clientStore = clientStore; this.clientStore = clientStore;
this.clientRegisteredNodesStore = clientRegisteredNodesStore; this.clientRegisteredNodesStore = clientRegisteredNodesStore;
@ -83,16 +80,21 @@ public class MapClientProvider implements ClientProvider {
}; };
} }
private MapClientEntity registerEntityForChanges(MapClientEntity origEntity) { private MapClientEntity<K> registerEntityForChanges(MapClientEntity<K> origEntity) {
final MapClientEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity)); final MapClientEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapClientEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapClientEntity<K>::isUpdated);
return res; 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 // 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 @Override
public void updateClient() { public void updateClient() {
LOG.tracef("updateClient(%s)%s", realm, origEntity.getId(), getShortStackTrace()); 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) { if (realm == null || realm.getId() == null) {
return MapClientProvider.ALWAYS_FALSE; return c -> false;
} }
String realmId = realm.getId(); String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId()); return entity -> Objects.equals(realmId, entity.getRealmId());
@ -145,7 +147,7 @@ public class MapClientProvider implements ClientProvider {
@Override @Override
public ClientModel addClient(RealmModel realm, String id, String clientId) { 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) { if (clientId == null) {
clientId = entityId.toString(); clientId = entityId.toString();
@ -153,7 +155,7 @@ public class MapClientProvider implements ClientProvider {
LOG.tracef("addClient(%s, %s, %s)%s", realm, id, clientId, getShortStackTrace()); 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.setClientId(clientId);
entity.setEnabled(true); entity.setEnabled(true);
entity.setStandardFlowEnabled(true); entity.setStandardFlowEnabled(true);
@ -213,7 +215,7 @@ public class MapClientProvider implements ClientProvider {
}); });
// TODO: ^^^^^^^ Up to here // TODO: ^^^^^^^ Up to here
tx.delete(UUID.fromString(id)); tx.delete(clientStore.getKeyConvertor().fromString(id));
return true; return true;
} }
@ -234,7 +236,7 @@ public class MapClientProvider implements ClientProvider {
LOG.tracef("getClientById(%s, %s)%s", realm, id, getShortStackTrace()); 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)) return (entity == null || ! entityRealmFilter(realm).test(entity))
? null ? null
: entityToAdapterFunc(realm).apply(entity); : entityToAdapterFunc(realm).apply(entity);
@ -268,7 +270,7 @@ public class MapClientProvider implements ClientProvider {
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.CLIENT_ID, Operator.ILIKE, "%" + clientId + "%"); .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); .sorted(COMPARE_BY_CLIENT_ID);
return paginatedStream(s, firstResult, maxResults).map(entityToAdapterFunc(realm)); return paginatedStream(s, firstResult, maxResults).map(entityToAdapterFunc(realm));
@ -276,7 +278,8 @@ public class MapClientProvider implements ClientProvider {
@Override @Override
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) { 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; if (entity == null) return;
@ -295,7 +298,8 @@ public class MapClientProvider implements ClientProvider {
@Override @Override
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) { 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; if (entity == null) return;
@ -306,7 +310,8 @@ public class MapClientProvider implements ClientProvider {
@Override @Override
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) { 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; if (entity == null) return null;
@ -326,7 +331,7 @@ public class MapClientProvider implements ClientProvider {
ModelCriteriaBuilder<ClientModel> mcb = clientStore.createCriteriaBuilder() ModelCriteriaBuilder<ClientModel> mcb = clientStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.SCOPE_MAPPING_ROLE, Operator.EQ, role.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 toRemove
.map(clientEntity -> session.clients().getClientById(realm, clientEntity.getId().toString())) .map(clientEntity -> session.clients().getClientById(realm, clientEntity.getId().toString()))
.filter(Objects::nonNull) .filter(Objects::nonNull)

View file

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

View file

@ -1,13 +1,13 @@
/* /*
* Copyright 2021 Red Hat, Inc. and/or its affiliates * Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,16 +16,155 @@
*/ */
package org.keycloak.models.map.clientscope; 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() { private final K id;
super(); 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) { public MapClientScopeEntity(K id, String realmId) {
super(id, 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.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; 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.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.utils.KeycloakModelUtils; 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 Logger LOG = Logger.getLogger(MapClientScopeProvider.class);
private static final Predicate<MapClientScopeEntity> ALWAYS_FALSE = c -> { return false; };
private final KeycloakSession session; private final KeycloakSession session;
private final MapKeycloakTransaction<UUID, MapClientScopeEntity, ClientScopeModel> tx; private final MapKeycloakTransaction<K, MapClientScopeEntity<K>, ClientScopeModel> tx;
private final MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore; private final MapStorage<K, MapClientScopeEntity<K>, ClientScopeModel> clientScopeStore;
private static final Comparator<MapClientScopeEntity> COMPARE_BY_NAME = Comparator.comparing(MapClientScopeEntity::getName); 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.session = session;
this.clientScopeStore = clientScopeStore; this.clientScopeStore = clientScopeStore;
this.tx = clientScopeStore.createTransaction(session); this.tx = clientScopeStore.createTransaction(session);
session.getTransactionManager().enlist(tx); session.getTransactionManager().enlist(tx);
} }
private MapClientScopeEntity registerEntityForChanges(MapClientScopeEntity origEntity) { private MapClientScopeEntity<K> registerEntityForChanges(MapClientScopeEntity<K> origEntity) {
final MapClientScopeEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity)); final MapClientScopeEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapClientScopeEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapClientScopeEntity<K>::isUpdated);
return res; 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 // 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) { if (realm == null || realm.getId() == null) {
return MapClientScopeProvider.ALWAYS_FALSE; return c -> false;
} }
String realmId = realm.getId(); String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId()); 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()); 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()); 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)); entity.setName(KeycloakModelUtils.convertClientScopeName(name));
if (tx.read(entity.getId()) != null) { if (tx.read(entity.getId()) != null) {
throw new ModelDuplicateException("Client scope exists: " + id); 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; return true;
} }
@ -159,14 +162,14 @@ public class MapClientScopeProvider implements ClientScopeProvider {
LOG.tracef("getClientScopeById(%s, %s)%s", realm, id, getShortStackTrace()); LOG.tracef("getClientScopeById(%s, %s)%s", realm, id, getShortStackTrace());
UUID uuid; K uuid;
try { try {
uuid = UUID.fromString(id); uuid = clientScopeStore.getKeyConvertor().fromStringSafe(id);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
return null; return null;
} }
MapClientScopeEntity entity = tx.read(uuid); MapClientScopeEntity<K> entity = tx.read(uuid);
return (entity == null || ! entityRealmFilter(realm).test(entity)) return (entity == null || ! entityRealmFilter(realm).test(entity))
? null ? null
: entityToAdapterFunc(realm).apply(entity); : entityToAdapterFunc(realm).apply(entity);

View file

@ -16,28 +16,25 @@
*/ */
package org.keycloak.models.map.clientscope; package org.keycloak.models.map.clientscope;
import java.util.UUID;
import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientScopeProvider; import org.keycloak.models.ClientScopeProvider;
import org.keycloak.models.ClientScopeProviderFactory; import org.keycloak.models.ClientScopeProviderFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory; 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; public MapClientScopeProviderFactory() {
super(MapClientScopeEntity.class, ClientScopeModel.class);
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("clientscope", UUID.class, MapClientScopeEntity.class, ClientScopeModel.class);
} }
@Override @Override
public ClientScopeProvider create(KeycloakSession session) { 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; package org.keycloak.models.map.common;
import org.keycloak.Config.Scope; 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.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
/** /**
* *
* @author hmlnarik * @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 PROVIDER_ID = "map";
public static final String CONFIG_STORAGE = "storage";
protected final Logger LOG = Logger.getLogger(getClass()); 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 @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 @Override
@ -41,11 +70,8 @@ public abstract class AbstractMapProviderFactory<T extends Provider> implements
} }
@Override @Override
public void close() { public void init(Scope config) {
} // Implementation of the map storage SPI
this.storageConfigScope = config.scope(CONFIG_STORAGE);
@Override
public String getId() {
return PROVIDER_ID;
} }
} }

View file

@ -19,6 +19,9 @@ package org.keycloak.models.map.common;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; 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.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper; 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<?>, ObjectReader> READERS = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<Class<?>, ObjectWriter> WRITERS = 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 { static {
JavaType type = TypeFactory.unknownType(); 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; import java.util.stream.Stream;
public class MapGroupAdapter extends AbstractGroupModel<MapGroupEntity> { public abstract class MapGroupAdapter<K> extends AbstractGroupModel<MapGroupEntity<K>> {
public MapGroupAdapter(KeycloakSession session, RealmModel realm, MapGroupEntity entity) { public MapGroupAdapter(KeycloakSession session, RealmModel realm, MapGroupEntity<K> entity) {
super(session, realm, entity); super(session, realm, entity);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getName() { public String getName() {
return entity.getName(); return entity.getName();

View file

@ -1,13 +1,13 @@
/* /*
* Copyright 2020 Red Hat, Inc. and/or its affiliates * Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,15 +17,118 @@
package org.keycloak.models.map.group; 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() { protected MapGroupEntity() {
super(); this.id = null;
this.realmId = null;
} }
public MapGroupEntity(UUID id, String realmId) { public MapGroupEntity(K id, String realmId) {
super(id, 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 org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import java.util.stream.Stream; 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.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.utils.StreamsUtil.paginatedStream; 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 static final Logger LOG = Logger.getLogger(MapGroupProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapGroupEntity, GroupModel> tx; final MapKeycloakTransaction<K, MapGroupEntity<K>, GroupModel> tx;
private final MapStorage<UUID, MapGroupEntity, GroupModel> groupStore; 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.session = session;
this.groupStore = groupStore; this.groupStore = groupStore;
this.tx = groupStore.createTransaction(session); this.tx = groupStore.createTransaction(session);
session.getTransactionManager().enlist(tx); session.getTransactionManager().enlist(tx);
} }
private MapGroupEntity registerEntityForChanges(MapGroupEntity origEntity) { private MapGroupEntity<K> registerEntityForChanges(MapGroupEntity<K> origEntity) {
final MapGroupEntity res = Serialization.from(origEntity); final MapGroupEntity<K> res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapGroupEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapGroupEntity<K>::isUpdated);
return res; 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 // 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 @Override
@ -74,15 +78,14 @@ public class MapGroupProvider implements GroupProvider {
LOG.tracef("getGroupById(%s, %s)%s", realm, id, getShortStackTrace()); LOG.tracef("getGroupById(%s, %s)%s", realm, id, getShortStackTrace());
K uid;
UUID uid;
try { try {
uid = UUID.fromString(id); uid = groupStore.getKeyConvertor().fromStringSafe(id);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
return null; return null;
} }
MapGroupEntity entity = tx.read(uid); MapGroupEntity<K> entity = tx.read(uid);
String realmId = realm.getId(); String realmId = realm.getId();
return (entity == null || ! Objects.equals(realmId, entity.getRealmId())) return (entity == null || ! Objects.equals(realmId, entity.getRealmId()))
? null ? null
@ -112,7 +115,7 @@ public class MapGroupProvider implements GroupProvider {
@Override @Override
public Stream<GroupModel> getGroupsStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) { public Stream<GroupModel> getGroupsStream(RealmModel realm, Stream<String> ids, String search, Integer first, Integer max) {
ModelCriteriaBuilder<GroupModel> mcb = groupStore.createCriteriaBuilder() 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()); .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
if (search != null) { if (search != null) {
@ -188,7 +191,7 @@ public class MapGroupProvider implements GroupProvider {
@Override @Override
public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) { public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) {
LOG.tracef("createGroup(%s, %s, %s, %s)%s", realm, id, name, toParent, getShortStackTrace()); 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"})} // Check Db constraint: uniqueConstraints = { @UniqueConstraint(columnNames = {"REALM_ID", "PARENT_GROUP", "NAME"})}
String parentId = toParent == null ? null : toParent.getId(); 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" ); 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.setName(name);
entity.setParentId(toParent == null ? null : toParent.getId()); entity.setParentId(toParent == null ? null : toParent.getId());
if (tx.read(entity.getId()) != null) { if (tx.read(entity.getId()) != null) {
@ -243,7 +246,7 @@ public class MapGroupProvider implements GroupProvider {
// TODO: ^^^^^^^ Up to here // TODO: ^^^^^^^ Up to here
tx.delete(UUID.fromString(group.getId())); tx.delete(groupStore.getKeyConvertor().fromString(group.getId()));
return true; return true;
} }
@ -264,7 +267,7 @@ public class MapGroupProvider implements GroupProvider {
.compare(SearchableFields.PARENT_ID, Operator.EQ, parentId) .compare(SearchableFields.PARENT_ID, Operator.EQ, parentId)
.compare(SearchableFields.NAME, Operator.EQ, group.getName()); .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()) { if (possibleSiblings.findAny().isPresent()) {
throw new ModelDuplicateException("Parent already contains subgroup named '" + group.getName() + "'"); 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.PARENT_ID, Operator.EQ, (Object) null)
.compare(SearchableFields.NAME, Operator.EQ, subGroup.getName()); .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()) { if (possibleSiblings.findAny().isPresent()) {
throw new ModelDuplicateException("There is already a top level group named '" + subGroup.getName() + "'"); 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() ModelCriteriaBuilder<GroupModel> mcb = groupStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.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 toRemove
.map(groupEntity -> session.groups().getGroupById(realm, groupEntity.getId().toString())) .map(groupEntity -> session.groups().getGroupById(realm, groupEntity.getId().toString()))
.forEach(groupModel -> groupModel.deleteRoleMapping(role)); .forEach(groupModel -> groupModel.deleteRoleMapping(role));

View file

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

View file

@ -16,17 +16,114 @@
*/ */
package org.keycloak.models.map.loginFailure; 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> * @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/ */
public class MapUserLoginFailureEntity extends AbstractUserLoginFailureEntity<UUID> { public class MapUserLoginFailureEntity<K> implements AbstractEntity<K> {
protected MapUserLoginFailureEntity() { private K id;
super(); 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) { public MapUserLoginFailureEntity(K id, String realmId, String userId) {
super(id, realmId, 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.MapStorage;
import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import static org.keycloak.common.util.StackUtil.getShortStackTrace; 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> * @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 static final Logger LOG = Logger.getLogger(MapUserLoginFailureProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
protected final MapKeycloakTransaction<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureTx; protected final MapKeycloakTransaction<K, MapUserLoginFailureEntity<K>, UserLoginFailureModel> userLoginFailureTx;
private final MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore; 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.session = session;
this.userLoginFailureStore = userLoginFailureStore; this.userLoginFailureStore = userLoginFailureStore;
@ -49,14 +48,19 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
session.getTransactionManager().enlistAfterCompletion(userLoginFailureTx); 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 // 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) { private MapUserLoginFailureEntity<K> registerEntityForChanges(MapUserLoginFailureEntity<K> origEntity) {
MapUserLoginFailureEntity res = userLoginFailureTx.read(origEntity.getId(), id -> Serialization.from(origEntity)); MapUserLoginFailureEntity<K> res = userLoginFailureTx.read(origEntity.getId(), id -> Serialization.from(origEntity));
userLoginFailureTx.updateIfChanged(origEntity.getId(), res, MapUserLoginFailureEntity::isUpdated); userLoginFailureTx.updateIfChanged(origEntity.getId(), res, MapUserLoginFailureEntity<K>::isUpdated);
return res; return res;
} }
@ -82,10 +86,10 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
LOG.tracef("addUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); 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) { 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); userLoginFailureTx.create(userLoginFailureEntity.getId(), userLoginFailureEntity);
} }
@ -101,7 +105,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
LOG.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); LOG.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace());
userLoginFailureTx.delete(UUID.randomUUID(), mcb); userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
} }
@Override @Override
@ -111,7 +115,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider {
LOG.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace()); LOG.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace());
userLoginFailureTx.delete(UUID.randomUUID(), mcb); userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
} }
@Override @Override

View file

@ -23,46 +23,58 @@ import org.keycloak.models.UserLoginFailureProvider;
import org.keycloak.models.UserLoginFailureProviderFactory; import org.keycloak.models.UserLoginFailureProviderFactory;
import org.keycloak.models.UserLoginFailureModel; import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.AbstractMapProviderFactory; import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage; import org.keycloak.provider.ProviderEvent;
import org.keycloak.models.map.storage.MapStorageProvider; import org.keycloak.provider.ProviderEventListener;
import java.util.UUID;
/** /**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a> * @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/ */
public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFactory<UserLoginFailureProvider> public class MapUserLoginFailureProviderFactory<K> extends AbstractMapProviderFactory<UserLoginFailureProvider, K, MapUserLoginFailureEntity<K>, UserLoginFailureModel>
implements UserLoginFailureProviderFactory { implements UserLoginFailureProviderFactory, ProviderEventListener {
private MapStorage<UUID, MapUserLoginFailureEntity, UserLoginFailureModel> userLoginFailureStore; private Runnable onClose;
public MapUserLoginFailureProviderFactory() {
super(MapUserLoginFailureEntity.class, UserLoginFailureModel.class);
}
@Override @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class); factory.register(this);
userLoginFailureStore = sp.getStorage("userLoginFailures", UUID.class, MapUserLoginFailureEntity.class, UserLoginFailureModel.class); onClose = () -> factory.unregister(this);
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());
}
});
} }
@Override @Override
public MapUserLoginFailureProvider create(KeycloakSession session) { public void close() {
return new MapUserLoginFailureProvider(session, userLoginFailureStore); 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.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.utils.ComponentUtil; 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 ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN = "actionTokenGeneratedByUserLifespan";
private static final String DEFAULT_SIGNATURE_ALGORITHM = "defaultSignatureAlgorithm"; private static final String DEFAULT_SIGNATURE_ALGORITHM = "defaultSignatureAlgorithm";
@ -75,15 +75,10 @@ public class MapRealmAdapter extends AbstractRealmModel<MapRealmEntity> implemen
private PasswordPolicy passwordPolicy; private PasswordPolicy passwordPolicy;
public MapRealmAdapter(KeycloakSession session, MapRealmEntity entity) { public MapRealmAdapter(KeycloakSession session, MapRealmEntity<K> entity) {
super(session, entity); super(session, entity);
} }
@Override
public String getId() {
return entity.getId();
}
@Override @Override
public String getName() { public String getName() {
return entity.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.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.utils.KeycloakModelUtils; 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 static final Logger LOG = Logger.getLogger(MapRealmProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
final MapKeycloakTransaction<String, MapRealmEntity, RealmModel> tx; final MapKeycloakTransaction<K, MapRealmEntity<K>, RealmModel> tx;
private final MapStorage<String, MapRealmEntity, RealmModel> realmStore; 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.session = session;
this.realmStore = realmStore; this.realmStore = realmStore;
this.tx = realmStore.createTransaction(session); this.tx = realmStore.createTransaction(session);
session.getTransactionManager().enlist(tx); 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 // 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) { private MapRealmEntity<K> registerEntityForChanges(MapRealmEntity<K> origEntity) {
final MapRealmEntity res = Serialization.from(origEntity); final MapRealmEntity<K> res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapRealmEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapRealmEntity<K>::isUpdated);
return res; return res;
} }
@ -76,20 +81,21 @@ public class MapRealmProvider implements RealmProvider {
throw new ModelDuplicateException("Realm with given name exists: " + name); throw new ModelDuplicateException("Realm with given name exists: " + name);
} }
if (id != null) { K kId = id == null ? null : realmStore.getKeyConvertor().fromString(id);
if (tx.read(id) != null) { if (kId != null) {
throw new ModelDuplicateException("Realm exists: " + id); if (tx.read(kId) != null) {
throw new ModelDuplicateException("Realm exists: " + kId);
} }
} else { } 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); entity.setName(name);
tx.create(id, entity); tx.create(kId, entity);
return entityToAdapter(entity); return entityToAdapter(entity);
} }
@ -99,7 +105,7 @@ public class MapRealmProvider implements RealmProvider {
LOG.tracef("getRealm(%s)%s", id, getShortStackTrace()); 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); return entity == null ? null : entityToAdapter(entity);
} }
@ -112,12 +118,12 @@ public class MapRealmProvider implements RealmProvider {
ModelCriteriaBuilder<RealmModel> mcb = realmStore.createCriteriaBuilder() ModelCriteriaBuilder<RealmModel> mcb = realmStore.createCriteriaBuilder()
.compare(SearchableFields.NAME, Operator.EQ, name); .compare(SearchableFields.NAME, Operator.EQ, name);
String realmId = tx.getUpdatedNotRemoved(mcb) K realmId = tx.getUpdatedNotRemoved(mcb)
.findFirst() .findFirst()
.map(MapRealmEntity::getId) .map(MapRealmEntity<K>::getId)
.orElse(null); .orElse(null);
//we need to go via session.realms() not to bypass cache //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 @Override
@ -167,7 +173,7 @@ public class MapRealmProvider implements RealmProvider {
}); });
// TODO: ^^^^^^^ Up to here // TODO: ^^^^^^^ Up to here
tx.delete(id); tx.delete(realmStore.getKeyConvertor().fromString(id));
return true; return true;
} }
@ -178,7 +184,7 @@ public class MapRealmProvider implements RealmProvider {
tx.getUpdatedNotRemoved(mcb) tx.getUpdatedNotRemoved(mcb)
.map(this::registerEntityForChanges) .map(this::registerEntityForChanges)
.forEach(MapRealmEntity::removeExpiredClientInitialAccesses); .forEach(MapRealmEntity<K>::removeExpiredClientInitialAccesses);
} }
//TODO move the following method to adapter //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.map.common.AbstractMapProviderFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.RealmProviderFactory; 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 public MapRealmProviderFactory() {
private MapStorage<String, MapRealmEntity, RealmModel> store; super(MapRealmEntity.class, RealmModel.class);
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("realm", String.class, MapRealmEntity.class, RealmModel.class);
} }
@Override @Override
public RealmProvider create(KeycloakSession session) { 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.RoleContainerModel;
import org.keycloak.models.utils.KeycloakModelUtils; 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); 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); super(session, realm, entity);
} }
@ -53,11 +53,6 @@ public class MapRoleAdapter extends AbstractRoleModel<MapRoleEntity> implements
entity.setDescription(description); entity.setDescription(description);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public void setName(String name) { public void setName(String name) {
entity.setName(name); entity.setName(name);

View file

@ -1,13 +1,13 @@
/* /*
* Copyright 2020 Red Hat, Inc. and/or its affiliates * Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,16 +16,135 @@
*/ */
package org.keycloak.models.map.role; 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() { protected MapRoleEntity() {
super(); this.id = null;
this.realmId = null;
} }
public MapRoleEntity(UUID id, String realmId) { public MapRoleEntity(K id, String realmId) {
super(id, 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 org.keycloak.models.map.common.Serialization;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; 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 static final Logger LOG = Logger.getLogger(MapRoleProvider.class);
private final KeycloakSession session; private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapRoleEntity, RoleModel> tx; final MapKeycloakTransaction<K, MapRoleEntity<K>, RoleModel> tx;
private final MapStorage<UUID, MapRoleEntity, RoleModel> roleStore; 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 @Override
public int compare(MapRoleEntity o1, MapRoleEntity o2) { public int compare(MapRoleEntity<?> o1, MapRoleEntity<?> o2) {
String r1 = o1 == null ? null : o1.getName(); String r1 = o1 == null ? null : o1.getName();
String r2 = o2 == null ? null : o2.getName(); String r2 = o2 == null ? null : o2.getName();
return r1 == r2 ? 0 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.session = session;
this.roleStore = roleStore; this.roleStore = roleStore;
this.tx = roleStore.createTransaction(session); this.tx = roleStore.createTransaction(session);
session.getTransactionManager().enlist(tx); 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 // 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) { private MapRoleEntity<K> registerEntityForChanges(MapRoleEntity<K> origEntity) {
final MapRoleEntity res = Serialization.from(origEntity); final MapRoleEntity<K> res = Serialization.from(origEntity);
tx.updateIfChanged(origEntity.getId(), res, MapRoleEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapRoleEntity<K>::isUpdated);
return res; return res;
} }
@ -88,11 +92,11 @@ public class MapRoleProvider implements RoleProvider {
throw new ModelDuplicateException("Role exists: " + id); 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()); 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.setName(name);
entity.setRealmId(realm.getId()); entity.setRealmId(realm.getId());
if (tx.read(entity.getId()) != null) { if (tx.read(entity.getId()) != null) {
@ -124,11 +128,11 @@ public class MapRoleProvider implements RoleProvider {
throw new ModelDuplicateException("Role exists: " + id); 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()); 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.setName(name);
entity.setClientRole(true); entity.setClientRole(true);
entity.setClientId(client.getId()); entity.setClientId(client.getId());
@ -168,13 +172,13 @@ public class MapRoleProvider implements RoleProvider {
.compare(SearchableFields.IS_COMPOSITE_ROLE, Operator.EQ, false); .compare(SearchableFields.IS_COMPOSITE_ROLE, Operator.EQ, false);
//remove role from realm-roles composites //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())) .filter(pair -> role.getId().equals(pair.getV()))
.collect(Collectors.toSet()) .collect(Collectors.toSet())
.forEach(pair -> { .forEach(pair -> {
MapRoleEntity origEntity = pair.getK(); MapRoleEntity<K> origEntity = pair.getK();
// //
// TODO: Investigate what this is for - the return value is ignored // 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.CLIENT_ID, Operator.EQ, client.getId())
.compare(SearchableFields.IS_COMPOSITE_ROLE, Operator.EQ, false); .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())) .filter(pair -> role.getId().equals(pair.getV()))
.collect(Collectors.toSet()) .collect(Collectors.toSet())
.forEach(pair -> { .forEach(pair -> {
MapRoleEntity origEntity = pair.getK(); MapRoleEntity<K> origEntity = pair.getK();
// //
// TODO: Investigate what this is for - the return value is ignored // TODO: Investigate what this is for - the return value is ignored
@ -223,7 +227,7 @@ public class MapRoleProvider implements RoleProvider {
}); });
// TODO: ^^^^^^^ Up to here // TODO: ^^^^^^^ Up to here
tx.delete(UUID.fromString(role.getId())); tx.delete(roleStore.getKeyConvertor().fromString(role.getId()));
return true; return true;
} }
@ -287,7 +291,7 @@ public class MapRoleProvider implements RoleProvider {
LOG.tracef("getRoleById(%s, %s)%s", realm, id, getShortStackTrace()); 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(); String realmId = realm.getId();
return (entity == null || ! Objects.equals(realmId, entity.getRealmId())) return (entity == null || ! Objects.equals(realmId, entity.getRealmId()))
? null ? null
@ -306,7 +310,7 @@ public class MapRoleProvider implements RoleProvider {
roleStore.createCriteriaBuilder().compare(SearchableFields.DESCRIPTION, 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); .sorted(COMPARE_BY_NAME);
return paginatedStream(s.map(entityToAdapterFunc(realm)), first, max); 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.NAME, Operator.ILIKE, "%" + search + "%"),
roleStore.createCriteriaBuilder().compare(SearchableFields.DESCRIPTION, 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); .sorted(COMPARE_BY_NAME);
return paginatedStream(s,first, max).map(entityToAdapterFunc(client.getRealm())); return paginatedStream(s,first, max).map(entityToAdapterFunc(client.getRealm()));

View file

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

View file

@ -54,7 +54,7 @@ public class MapKeycloakTransaction<K, V extends AbstractEntity<K>, M> implement
@Override @Override
public void commit() { public void commit() {
log.trace("Commit"); log.tracef("Commit - %s", map);
if (rollback) { if (rollback) {
throw new RuntimeException("Rollback only!"); 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 // This is for possibility to lookup for session by id, which was created in this transaction
public V read(K key) { 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) { 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. * 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. * 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 * @param mcb
* @return * @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. * 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}. * @param key Key of the object. Must not be {@code null}.
* @return See description * @return See description
* @throws NullPointerException if the {@code key} is {@code null} * @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 * If possible, do <i>not</i> delay filtering after the models are reconstructed from
* storage entities, in most cases this would be highly inefficient. * storage entities, in most cases this would be highly inefficient.
* *
* @return See description * @return See description. Never returns {@code null}
*/ */
ModelCriteriaBuilder<M> createCriteriaBuilder(); 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 * 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. * (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); 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 { 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 <K> type of the primary key
* @param <V> type of the value * @param <V> type of the value
* @param name Name of the storage * @param name Name of the storage
* @param flags * @param flags
* @return * @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; package org.keycloak.models.map.storage;
import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.component.ComponentFactory;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
/** /**
* *
* @author hmlnarik * @author hmlnarik
*/ */
public interface MapStorageProviderFactory extends ProviderFactory<MapStorageProvider> { public interface MapStorageProviderFactory extends ProviderFactory<MapStorageProvider>, ComponentFactory<MapStorageProvider, MapStorageProvider> {
public enum Flag { public enum Flag {
INITIALIZE_EMPTY, INITIALIZE_EMPTY,
LOCAL 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 * Creates and returns a new instance of {@code ModelCriteriaBuilder} that
* combines the given builders with the Boolean OR operator. * combines the given builders with the Boolean OR operator.
* <p> * <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}. * (i.e. empty disjunction) is always {@code false}.
* *
* <pre> * <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.concurrent.ConcurrentMap;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.keycloak.models.map.storage.MapModelCriteriaBuilder.UpdatePredicatesFunc; import org.keycloak.models.map.storage.MapModelCriteriaBuilder.UpdatePredicatesFunc;
import org.keycloak.models.map.storage.StringKeyConvertor;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects; import java.util.Objects;
import java.util.function.Predicate; 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 ConcurrentMap<K, V> store = new ConcurrentHashMap<>();
private final Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates; private final Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates;
private final StringKeyConvertor<K> keyConvertor;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ConcurrentHashMapStorage(Class<M> modelClass) { public ConcurrentHashMapStorage(Class<M> modelClass, StringKeyConvertor<K> keyConvertor) {
this.fieldPredicates = MapFieldPredicates.getPredicates(modelClass); this.fieldPredicates = MapFieldPredicates.getPredicates(modelClass);
this.keyConvertor = keyConvertor;
} }
@Override @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; return sessionTransaction == null ? new MapKeycloakTransaction<>(this) : (MapKeycloakTransaction<K, V, M>) sessionTransaction;
} }
@Override
public StringKeyConvertor<K> getKeyConvertor() {
return keyConvertor;
}
@Override @Override
public Stream<V> read(ModelCriteriaBuilder<M> criteria) { public Stream<V> read(ModelCriteriaBuilder<M> criteria) {

View file

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

View file

@ -16,10 +16,23 @@
*/ */
package org.keycloak.models.map.storage.chm; package org.keycloak.models.map.storage.chm;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.Config.Scope; 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.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.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; 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.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.Serialization; 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.MapStorageProviderFactory;
import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; 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 * @author hmlnarik
*/ */
public class ConcurrentHashMapStorageProviderFactory implements MapStorageProviderFactory { public class ConcurrentHashMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>,MapStorageProviderFactory {
public static final String PROVIDER_ID = "concurrenthashmap"; 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 ConcurrentHashMap<String, ConcurrentHashMapStorage<?,?,?>> storages = new ConcurrentHashMap<>();
private final Map<String, StringKeyConvertor> keyConvertors = new HashMap<>();
private File storageDirectory; 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 @Override
public MapStorageProvider create(KeycloakSession session) { public MapStorageProvider create(KeycloakSession session) {
return new ConcurrentHashMapStorageProvider(this); return new ConcurrentHashMapStorageProvider(this);
} }
@Override @Override
public void init(Scope config) { public void init(Scope config) {
final String dir = config.get("dir"); if (config instanceof ComponentModelScope) {
if (dir == null || dir.trim().isEmpty()) { this.suffix = "-" + ((ComponentModelScope) config).getComponentId();
LOG.warn("No directory set, created objects will not survive server restart");
this.storageDirectory = null;
} else { } else {
File f = new File(dir); this.suffix = "";
try { }
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()); Files.createDirectories(f.toPath());
if (f.exists()) { if (f.exists()) {
this.storageDirectory = f; 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); LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null; 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 @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {
} }
@ -88,17 +161,17 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
storages.forEach(this::storeMap); storages.forEach(this::storeMap);
} }
private void storeMap(String fileName, ConcurrentHashMapStorage<?, ?, ?> store) { private void storeMap(String mapName, ConcurrentHashMapStorage<?, ?, ?> store) {
if (fileName != null) { if (mapName != null) {
File f = getFile(fileName); File f = getFile(mapName);
try { try {
if (storageDirectory != null && storageDirectory.exists()) { if (storageDirectory != null) {
LOG.debugf("Storing contents to %s", f.getCanonicalPath()); LOG.debugf("Storing contents to %s", f.getCanonicalPath());
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final ModelCriteriaBuilder readAllCriteria = store.createCriteriaBuilder(); final ModelCriteriaBuilder readAllCriteria = store.createCriteriaBuilder();
Serialization.MAPPER.writeValue(f, store.read(readAllCriteria)); Serialization.MAPPER.writeValue(f, store.read(readAllCriteria));
} else { } 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) { } catch (IOException ex) {
throw new RuntimeException(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) { 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; ConcurrentHashMapStorage<K, V, M> store;
if (modelType == UserSessionModel.class) { if (modelType == UserSessionModel.class) {
ConcurrentHashMapStorage clientSessionStore = ConcurrentHashMapStorage clientSessionStore = getStorage(MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class); store = new UserSessionConcurrentHashMapStorage(clientSessionStore, kc) {
store = new UserSessionConcurrentHashMapStorage<>(clientSessionStore); @Override
public String toString() {
return "ConcurrentHashMapStorage(" + mapName + suffix + ")";
}
};
} else { } else {
store = new ConcurrentHashMapStorage<>(modelType); store = new ConcurrentHashMapStorage(modelType, kc) {
@Override
public String toString() {
return "ConcurrentHashMapStorage(" + mapName + suffix + ")";
}
};
} }
if (! flags.contains(Flag.INITIALIZE_EMPTY)) { if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
final File f = getFile(fileName); final File f = getFile(mapName);
if (f != null && f.exists()) { if (f != null && f.exists()) {
try { try {
LOG.debugf("Restoring contents from %s", f.getCanonicalPath()); LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
@ -141,17 +228,38 @@ public class ConcurrentHashMapStorageProviderFactory implements MapStorageProvid
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(String name, Class<V> valueType, Class<M> modelType, Flag... flags) {
Class<K> keyType, 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); 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)); return (ConcurrentHashMapStorage<K, V, M>) storages.computeIfAbsent(name, n -> loadMap(name, valueType, modelType, f));
} }
private File getFile(String fileName) { private File getFile(String fileName) {
return storageDirectory == null return storageDirectory == null
? 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.MapKeycloakTransaction;
import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.userSession.AbstractAuthenticatedClientSessionEntity; import org.keycloak.models.map.storage.StringKeyConvertor;
import org.keycloak.models.map.userSession.AbstractUserSessionEntity; import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -34,15 +35,15 @@ import java.util.stream.Collectors;
* *
* @author hmlnarik * @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); super(UserSessionConcurrentHashMapStorage.this);
this.clientSessionTr = clientSessionTr; this.clientSessionTr = clientSessionTr;
} }
@ -65,15 +66,16 @@ public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapSto
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public UserSessionConcurrentHashMapStorage(ConcurrentHashMapStorage<K, AbstractAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore) { public UserSessionConcurrentHashMapStorage(ConcurrentHashMapStorage<K, MapAuthenticatedClientSessionEntity<K>, AuthenticatedClientSessionModel> clientSessionStore,
super(UserSessionModel.class); StringKeyConvertor<K> keyConvertor) {
super(UserSessionModel.class, keyConvertor);
this.clientSessionStore = clientSessionStore; this.clientSessionStore = clientSessionStore;
} }
@Override @Override
@SuppressWarnings("unchecked") @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); 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.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class MapUserAdapter extends AbstractUserModel<MapUserEntity> { public abstract class MapUserAdapter<K> extends AbstractUserModel<MapUserEntity<K>> {
public MapUserAdapter(KeycloakSession session, RealmModel realm, MapUserEntity entity) { public MapUserAdapter(KeycloakSession session, RealmModel realm, MapUserEntity<K> entity) {
super(session, realm, entity); super(session, realm, entity);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public String getUsername() { public String getUsername() {
return entity.getUsername(); return entity.getUsername();

View file

@ -1,13 +1,13 @@
/* /*
* Copyright 2020 Red Hat, Inc. and/or its affiliates * Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -17,18 +17,355 @@
package org.keycloak.models.map.user; 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.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() { protected MapUserEntity() {
super(); this.id = null;
this.realmId = null;
} }
public MapUserEntity(UUID id, String realmId) { public MapUserEntity(K id, String realmId) {
super(id, 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.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; 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.models.UserModel.USERNAME;
import static org.keycloak.utils.StreamsUtil.paginatedStream; 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 Logger LOG = Logger.getLogger(MapUserProvider.class);
private static final Predicate<MapUserEntity> ALWAYS_FALSE = c -> { return false; };
private final KeycloakSession session; private final KeycloakSession session;
final MapKeycloakTransaction<UUID, MapUserEntity, UserModel> tx; final MapKeycloakTransaction<K, MapUserEntity<K>, UserModel> tx;
private final MapStorage<UUID, MapUserEntity, UserModel> userStore; 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.session = session;
this.userStore = store; this.userStore = store;
this.tx = userStore.createTransaction(session); this.tx = userStore.createTransaction(session);
session.getTransactionManager().enlist(tx); session.getTransactionManager().enlist(tx);
} }
private MapUserEntity registerEntityForChanges(MapUserEntity origEntity) { private MapUserEntity<K> registerEntityForChanges(MapUserEntity<K> origEntity) {
MapUserEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity)); MapUserEntity<K> res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
tx.updateIfChanged(origEntity.getId(), res, MapUserEntity::isUpdated); tx.updateIfChanged(origEntity.getId(), res, MapUserEntity<K>::isUpdated);
return res; 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 // 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 @Override
public boolean checkEmailUniqueness(RealmModel realm, String email) { 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) { if (realm == null || realm.getId() == null) {
return MapUserProvider.ALWAYS_FALSE; return c -> false;
} }
String realmId = realm.getId(); String realmId = realm.getId();
return entity -> Objects.equals(realmId, entity.getRealmId()); 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."); 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 { try {
return getEntityById(realm, UUID.fromString(id)); return getEntityById(realm, userStore.getKeyConvertor().fromString(id));
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
return Optional.empty(); return Optional.empty();
} }
} }
private MapUserEntity getRegisteredEntityByIdOrThrow(RealmModel realm, String id) { private MapUserEntity<K> getRegisteredEntityByIdOrThrow(RealmModel realm, String id) {
return getEntityById(realm, id) return getEntityById(realm, id)
.map(this::registerEntityForChanges) .map(this::registerEntityForChanges)
.orElseThrow(this::userDoesntExistException); .orElseThrow(this::userDoesntExistException);
} }
private Optional<MapUserEntity> getEntityById(RealmModel realm, UUID id) { private Optional<MapUserEntity<K>> getEntityById(RealmModel realm, K id) {
MapUserEntity mapUserEntity = tx.read(id); MapUserEntity<K> mapUserEntity = tx.read(id);
if (mapUserEntity != null && entityRealmFilter(realm).test(mapUserEntity)) { if (mapUserEntity != null && entityRealmFilter(realm).test(mapUserEntity)) {
return Optional.of(mapUserEntity); return Optional.of(mapUserEntity);
} }
@ -145,7 +147,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
return Optional.empty(); 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); 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) { public Stream<FederatedIdentityModel> getFederatedIdentitiesStream(RealmModel realm, UserModel user) {
LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace()); LOG.tracef("getFederatedIdentitiesStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId()) return getEntityById(realm, user.getId())
.map(AbstractUserEntity::getFederatedIdentities).orElseGet(Stream::empty) .map(MapUserEntity::getFederatedIdentities).orElseGet(Stream::empty)
.map(UserFederatedIdentityEntity::toModel); .map(UserFederatedIdentityEntity::toModel);
} }
@ -249,7 +251,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public Stream<UserConsentModel> getConsentsStream(RealmModel realm, String userId) { public Stream<UserConsentModel> getConsentsStream(RealmModel realm, String userId) {
LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace()); LOG.tracef("getConsentByClientStream(%s, %s)%s", realm, userId, getShortStackTrace());
return getEntityById(realm, userId) return getEntityById(realm, userId)
.map(AbstractUserEntity::getUserConsents) .map(MapUserEntity::getUserConsents)
.orElse(Stream.empty()) .orElse(Stream.empty())
.map(consent -> UserConsentEntity.toModel(realm, consent)); .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) { public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) {
LOG.tracef("updateConsent(%s, %s, %s)%s", realm, userId, consent, getShortStackTrace()); 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()); UserConsentEntity userConsentEntity = user.getUserConsent(consent.getClient().getId());
if (userConsentEntity == null) { if (userConsentEntity == null) {
throw new ModelException("Consent not found for client [" + consent.getClient().getId() + "] and user [" + userId + "]"); 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" ); 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) { if (tx.read(entityId) != null) {
throw new ModelDuplicateException("User exists: " + entityId); 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.setUsername(username.toLowerCase());
entity.setCreatedTimestamp(Time.currentTimeMillis()); entity.setCreatedTimestamp(Time.currentTimeMillis());
@ -366,7 +368,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
ModelCriteriaBuilder<UserModel> mcb = userStore.createCriteriaBuilder() ModelCriteriaBuilder<UserModel> mcb = userStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
tx.delete(UUID.randomUUID(), mcb); tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
} }
@Override @Override
@ -376,7 +378,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId); .compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId);
tx.delete(UUID.randomUUID(), mcb); tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), mcb);
} }
@Override @Override
@ -386,7 +388,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId); .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) s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.setFederationLink(null)); .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.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, roleId); .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) s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.removeRolesMembership(roleId)); .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.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.ASSIGNED_GROUP, Operator.EQ, groupId); .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) s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.removeGroupsMembership(groupId)); .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.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.CONSENT_FOR_CLIENT, Operator.EQ, clientId); .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) s.map(this::registerEntityForChanges)
.forEach(userEntity -> userEntity.removeUserConsent(clientId)); .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.REALM_ID, Operator.EQ, clientScope.getRealm().getId())
.compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId); .compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) { try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
s.flatMap(AbstractUserEntity::getUserConsents) s.flatMap(MapUserEntity::getUserConsents)
.forEach(consent -> consent.removeGrantedClientScopesIds(clientScopeId)); .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.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, Operator.EQ, componentId); .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(); String providerIdS = new StorageId(componentId, "").getId();
s.forEach(removeConsentsForExternalClient(providerIdS)); s.forEach(removeConsentsForExternalClient(providerIdS));
} }
} }
} }
private Consumer<MapUserEntity> removeConsentsForExternalClient(String idPrefix) { private Consumer<MapUserEntity<K>> removeConsentsForExternalClient(String idPrefix) {
return userEntity -> { return userEntity -> {
List<String> consentClientIds = userEntity.getUserConsents() List<String> consentClientIds = userEntity.getUserConsents()
.map(UserConsentEntity::getClientId) .map(UserConsentEntity::getClientId)
@ -494,7 +496,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
ModelCriteriaBuilder<UserModel> mcb = userStore.createCriteriaBuilder() ModelCriteriaBuilder<UserModel> mcb = userStore.createCriteriaBuilder()
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); .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) s.map(this::registerEntityForChanges)
.forEach(entity -> entity.addRolesMembership(roleId)); .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.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.USERNAME, Operator.ILIKE, username); .compare(SearchableFields.USERNAME, Operator.ILIKE, username);
try (Stream<MapUserEntity> s = tx.getUpdatedNotRemoved(mcb)) { try (Stream<MapUserEntity<K>> s = tx.getUpdatedNotRemoved(mcb)) {
return s.findFirst() return s.findFirst()
.map(entityToAdapterFunc(realm)).orElse(null); .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.REALM_ID, Operator.EQ, realm.getId())
.compare(SearchableFields.EMAIL, Operator.EQ, email); .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)) .filter(userEntity -> Objects.equals(userEntity.getEmail(), email))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (usersWithEmail.isEmpty()) return null; 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."); 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 (!realm.isDuplicateEmailsAllowed()) {
if (userEntity.getEmail() != null && !userEntity.getEmail().equals(userEntity.getEmailConstraint())) { 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 @Override
public boolean checkEmailUniqueness(RealmModel realm, String email) { public boolean checkEmailUniqueness(RealmModel realm, String email) {
return getUserByEmail(realm, email) != null; 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); mcb = mcb.compare(SearchableFields.ASSIGNED_GROUP, Operator.IN, authorizedGroups);
} }
Stream<MapUserEntity> usersStream = tx.getUpdatedNotRemoved(mcb) Stream<MapUserEntity<K>> usersStream = tx.getUpdatedNotRemoved(mcb)
.sorted(AbstractUserEntity.COMPARE_BY_USERNAME); // Sort before paginating .sorted(MapUserEntity.COMPARE_BY_USERNAME); // Sort before paginating
return paginatedStream(usersStream, firstResult, maxResults) // paginate if necessary return paginatedStream(usersStream, firstResult, maxResults) // paginate if necessary
.map(entityToAdapterFunc(realm)) .map(entityToAdapterFunc(realm))
@ -739,9 +746,9 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
@Override @Override
public boolean removeUser(RealmModel realm, UserModel user) { public boolean removeUser(RealmModel realm, UserModel user) {
String userId = user.getId(); String userId = user.getId();
Optional<MapUserEntity> userById = getEntityById(realm, userId); Optional<MapUserEntity<K>> userById = getEntityById(realm, userId);
if (userById.isPresent()) { if (userById.isPresent()) {
tx.delete(UUID.fromString(userId)); tx.delete(userStore.getKeyConvertor().fromString(userId));
return true; return true;
} }
@ -766,7 +773,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
.ifPresent(updateCredential(cred)); .ifPresent(updateCredential(cred));
} }
private Consumer<MapUserEntity> updateCredential(CredentialModel credentialModel) { private Consumer<MapUserEntity<K>> updateCredential(CredentialModel credentialModel) {
return user -> { return user -> {
UserCredentialEntity credentialEntity = user.getCredential(credentialModel.getId()); UserCredentialEntity credentialEntity = user.getCredential(credentialModel.getId());
if (credentialEntity == null) return; if (credentialEntity == null) return;
@ -811,7 +818,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) { public Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, UserModel user) {
LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace()); LOG.tracef("getStoredCredentialsStream(%s, %s)%s", realm, user.getId(), getShortStackTrace());
return getEntityById(realm, user.getId()) return getEntityById(realm, user.getId())
.map(AbstractUserEntity::getCredentials) .map(MapUserEntity::getCredentials)
.orElseGet(Stream::empty) .orElseGet(Stream::empty)
.map(UserCredentialEntity::toModel); .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) { 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()); LOG.tracef("moveCredentialTo(%s, %s, %s, %s)%s", realm, user.getId(), id, newPreviousCredentialId, getShortStackTrace());
String userId = user.getId(); String userId = user.getId();
MapUserEntity userEntity = getRegisteredEntityById(realm, userId).orElse(null); MapUserEntity<K> userEntity = getRegisteredEntityById(realm, userId).orElse(null);
if (userEntity == null) { if (userEntity == null) {
LOG.warnf("User with id: [%s] not found", userId); LOG.warnf("User with id: [%s] not found", userId);
return false; return false;

View file

@ -18,34 +18,29 @@
package org.keycloak.models.map.user; package org.keycloak.models.map.user;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.models.UserProviderFactory; import org.keycloak.models.UserProviderFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory; 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 * @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; public MapUserProviderFactory() {
super(MapUserEntity.class, UserModel.class);
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("users", UUID.class, MapUserEntity.class, UserModel.class);
} }
@Override @Override
public UserProvider create(KeycloakSession session) { 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> * @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 KeycloakSession session;
protected final RealmModel realm; protected final RealmModel realm;
protected ClientModel client; protected ClientModel client;
protected UserSessionModel userSession; protected UserSessionModel userSession;
protected final E entity; protected final MapAuthenticatedClientSessionEntity<K> entity;
public AbstractAuthenticatedClientSessionModel(KeycloakSession session, RealmModel realm, ClientModel client, public AbstractAuthenticatedClientSessionModel(KeycloakSession session, RealmModel realm, ClientModel client,
UserSessionModel userSession, E entity) { UserSessionModel userSession, MapAuthenticatedClientSessionEntity<K> entity) {
Objects.requireNonNull(entity, "entity"); Objects.requireNonNull(entity, "entity");
Objects.requireNonNull(realm, "realm"); Objects.requireNonNull(realm, "realm");
Objects.requireNonNull(client, "client"); 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> * @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 KeycloakSession session;
protected final RealmModel realm; 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(entity, "entity");
Objects.requireNonNull(realm, "realm"); Objects.requireNonNull(realm, "realm");
@ -39,18 +39,4 @@ public abstract class AbstractUserSessionModel<E extends AbstractEntity> impleme
this.realm = realm; this.realm = realm;
this.entity = entity; 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> * @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, public MapAuthenticatedClientSessionAdapter(KeycloakSession session, RealmModel realm, ClientModel client,
UserSessionModel userSession, MapAuthenticatedClientSessionEntity entity) { UserSessionModel userSession, MapAuthenticatedClientSessionEntity<K> entity) {
super(session, realm, client, userSession, entity); super(session, realm, client, userSession, entity);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public int getTimestamp() { public int getTimestamp() {
return entity.getTimestamp(); return entity.getTimestamp();

View file

@ -16,18 +16,190 @@
*/ */
package org.keycloak.models.map.userSession; 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> * @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() { private K id;
super(); 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) { public MapAuthenticatedClientSessionEntity(K id, String userSessionId, String realmId, String clientId, boolean offline) {
super(id, userSessionId, realmId, clientId, 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.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a> * @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); super(session, realm, entity);
} }
@Override
public String getId() {
return entity.getId().toString();
}
@Override @Override
public RealmModel getRealm() { public RealmModel getRealm() {
return realm; return realm;
@ -134,7 +131,7 @@ public abstract class MapUserSessionAdapter extends AbstractUserSessionModel<Map
@Override @Override
public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) { public AuthenticatedClientSessionModel getAuthenticatedClientSessionByClient(String clientUUID) {
UUID clientSessionId = entity.getAuthenticatedClientSessions().get(clientUUID); String clientSessionId = entity.getAuthenticatedClientSessions().get(clientUUID);
if (clientSessionId == null) { if (clientSessionId == null) {
return null; return null;
@ -220,4 +217,18 @@ public abstract class MapUserSessionAdapter extends AbstractUserSessionModel<Map
public String toString() { public String toString() {
return String.format("%s@%08x", getId(), hashCode()); 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; package org.keycloak.models.map.userSession;
import org.keycloak.common.util.Time;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; 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> * @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/ */
public class MapUserSessionEntity extends AbstractUserSessionEntity<UUID> { public class MapUserSessionEntity<K> implements AbstractEntity<K> {
protected MapUserSessionEntity() { private K id;
super();
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) { public MapUserSessionEntity(K id, String realmId) {
super(id, realmId); Objects.requireNonNull(id, "id");
Objects.requireNonNull(realmId, "realmId");
this.id = id;
this.realmId = realmId;
} }
public MapUserSessionEntity(UUID id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, public MapUserSessionEntity(K id, RealmModel realm, UserModel user, String loginUsername, String ipAddress,
String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId,
boolean offline) { boolean offline) {
super(id, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId, offline); this.id = id;
this.realmId = realm.getId();
this.userId = user.getId();
this.loginUsername = loginUsername;
this.ipAddress = ipAddress;
this.authMethod = authMethod;
this.rememberMe = rememberMe;
this.brokerSessionId = brokerSessionId;
this.brokerUserId = brokerUserId;
this.started = Time.currentTime();
this.lastSessionRefresh = started;
this.offline = offline;
}
@Override
public K getId() {
return this.id;
}
@Override
public boolean isUpdated() {
return this.updated;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.updated |= !Objects.equals(this.realmId, realmId);
this.realmId = realmId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.updated |= !Objects.equals(this.userId, userId);
this.userId = userId;
}
public String getBrokerSessionId() {
return brokerSessionId;
}
public void setBrokerSessionId(String brokerSessionId) {
this.updated |= !Objects.equals(this.brokerSessionId, brokerSessionId);
this.brokerSessionId = brokerSessionId;
}
public String getBrokerUserId() {
return brokerUserId;
}
public void setBrokerUserId(String brokerUserId) {
this.updated |= !Objects.equals(this.brokerUserId, brokerUserId);
this.brokerUserId = brokerUserId;
}
public String getLoginUsername() {
return loginUsername;
}
public void setLoginUsername(String loginUsername) {
this.updated |= !Objects.equals(this.loginUsername, loginUsername);
this.loginUsername = loginUsername;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.updated |= !Objects.equals(this.ipAddress, ipAddress);
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.updated |= !Objects.equals(this.authMethod, authMethod);
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.updated |= this.rememberMe != rememberMe;
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}
public void setStarted(int started) {
this.updated |= this.started != started;
this.started = started;
}
public int getLastSessionRefresh() {
return lastSessionRefresh;
}
public void setLastSessionRefresh(int lastSessionRefresh) {
this.updated |= this.lastSessionRefresh != lastSessionRefresh;
this.lastSessionRefresh = lastSessionRefresh;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.updated |= this.expiration != expiration;
this.expiration = expiration;
}
public Map<String, String> getNotes() {
return notes;
}
public String getNote(String name) {
return notes.get(name);
}
public void setNotes(Map<String, String> notes) {
this.updated |= !Objects.equals(this.notes, notes);
this.notes = notes;
}
public String removeNote(String name) {
String note = this.notes.remove(name);
this.updated |= note != null;
return note;
}
public void addNote(String name, String value) {
this.updated |= !Objects.equals(this.notes.put(name, value), value);
}
public UserSessionModel.State getState() {
return state;
}
public void setState(UserSessionModel.State state) {
this.updated |= !Objects.equals(this.state, state);
this.state = state;
}
public Map<String, String> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
public void setAuthenticatedClientSessions(Map<String, String> authenticatedClientSessions) {
this.updated |= !Objects.equals(this.authenticatedClientSessions, authenticatedClientSessions);
this.authenticatedClientSessions = authenticatedClientSessions;
}
public void addAuthenticatedClientSession(String clientId, String clientSessionId) {
this.updated |= !Objects.equals(this.authenticatedClientSessions.put(clientId, clientSessionId), clientSessionId);
}
public String removeAuthenticatedClientSession(String clientId) {
String entity = this.authenticatedClientSessions.remove(clientId);
this.updated |= entity != null;
return entity;
}
public void clearAuthenticatedClientSessions() {
this.updated |= !authenticatedClientSessions.isEmpty();
this.authenticatedClientSessions.clear();
}
public boolean isOffline() {
return offline;
}
public void setOffline(boolean offline) {
this.updated |= this.offline != offline;
this.offline = offline;
}
public UserSessionModel.SessionPersistenceState getPersistenceState() {
return persistenceState;
}
public void setPersistenceState(UserSessionModel.SessionPersistenceState persistenceState) {
this.updated |= !Objects.equals(this.persistenceState, persistenceState);
this.persistenceState = persistenceState;
}
@Override
public String toString() {
return String.format("%s@%08x", getId(), hashCode());
} }
} }

View file

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

View file

@ -28,11 +28,11 @@ import java.util.stream.Collectors;
public class AbstractUserEntityCredentialsOrderTest { public class AbstractUserEntityCredentialsOrderTest {
private AbstractUserEntity<Integer> user; private MapUserEntity<Integer> user;
@Before @Before
public void init() { public void init() {
user = new AbstractUserEntity<Integer>(1, "realmId") {}; user = new MapUserEntity<Integer>(1, "realmId") {};
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++) {
UserCredentialEntity credentialModel = new UserCredentialEntity(); 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> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public class StoreFactorySpi implements Spi { public class StoreFactorySpi implements Spi {
public static final String NAME = "authorizationPersister";
@Override @Override
public boolean isInternal() { public boolean isInternal() {
return true; return true;
@ -33,7 +36,7 @@ public class StoreFactorySpi implements Spi {
@Override @Override
public String getName() { public String getName() {
return "authorizationPersister"; return NAME;
} }
@Override @Override

View file

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

View file

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

View file

@ -40,7 +40,14 @@ public interface UserSessionProvider extends Provider {
KeycloakSession getKeycloakSession(); KeycloakSession getKeycloakSession();
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession); 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); 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 KeycloakSessionFactory factory;
private boolean componentCachingAvailable; private boolean componentCachingAvailable;
private boolean componentCachingEnabled; private boolean componentCachingEnabled;
private Boolean componentCachingForced;
@Override @Override
public void init(Scope config) { public void init(Scope config) {
this.componentCachingEnabled = config.getBoolean("cachingEnabled", true); this.componentCachingEnabled = config.getBoolean("cachingEnabled", true);
this.componentCachingForced = config.getBoolean("cachingForced", false);
} }
@Override @Override
@ -72,7 +74,12 @@ public class DefaultComponentFactoryProviderFactory implements ComponentFactoryP
if (! componentCachingEnabled) { if (! componentCachingEnabled) {
LOG.warn("Caching of components disabled by the configuration which may have performance impact."); LOG.warn("Caching of components disabled by the configuration which may have performance impact.");
} else if (! componentCachingAvailable) { } 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); Scope scope = Config.scope(factory.getSpi(clazz).getName(), provider);
ComponentModelScope configScope = new ComponentModelScope(scope, cm); ComponentModelScope configScope = new ComponentModelScope(scope, cm);
return this.componentCachingAvailable ProviderFactory<T> providerFactory;
? componentsMap.get().computeIfAbsent(componentId, cId -> initializeFactory(clazz, realmId, componentId, newFactory, configScope)) if (this.componentCachingAvailable) {
: initializeFactory(clazz, realmId, componentId, newFactory, configScope); providerFactory = componentsMap.get().computeIfAbsent(componentId, cId -> initializeFactory(clazz, realmId, componentId, newFactory, configScope));
} else {
providerFactory = initializeFactory(clazz, realmId, componentId, newFactory, configScope);
}
return providerFactory;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View file

@ -82,7 +82,9 @@
"mapStorage": { "mapStorage": {
"provider": "${keycloak.mapStorage.provider:concurrenthashmap}", "provider": "${keycloak.mapStorage.provider:concurrenthashmap}",
"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