Added ModelIllegalStateException to handle lazy loading exception.
Closes #9645
This commit is contained in:
parent
64cbbde7cf
commit
df7ddbf9b3
14 changed files with 236 additions and 70 deletions
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022. Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage.jpa;
|
||||||
|
|
||||||
|
import org.keycloak.models.ModelIllegalStateException;
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all delegate providers for the JPA storage.
|
||||||
|
*
|
||||||
|
* Wraps the delegate so that it can be safely updated during lazy loading.
|
||||||
|
*/
|
||||||
|
public abstract class JpaDelegateProvider<T extends JpaRootEntity & AbstractEntity> {
|
||||||
|
private T delegate;
|
||||||
|
|
||||||
|
protected JpaDelegateProvider(T delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T getDelegate() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the new entity.
|
||||||
|
*
|
||||||
|
* Will throw {@link ModelIllegalStateException} if the entity has been deleted or changed in the meantime.
|
||||||
|
*/
|
||||||
|
protected void setDelegate(T newDelegate) {
|
||||||
|
if (newDelegate == null) {
|
||||||
|
throw new ModelIllegalStateException("Unable to retrieve entity: " + delegate.getClass().getName() + "#" + delegate.getId());
|
||||||
|
}
|
||||||
|
if (newDelegate.getVersion() != delegate.getVersion()) {
|
||||||
|
throw new ModelIllegalStateException("Version of entity changed between two loads: " + delegate.getClass().getName() + "#" + delegate.getId());
|
||||||
|
}
|
||||||
|
this.delegate = newDelegate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models.map.storage.jpa;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for all root entities in the JPA storage.
|
||||||
|
*/
|
||||||
|
public interface JpaRootEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of the JPA entity used for optimistic locking
|
||||||
|
*/
|
||||||
|
int getVersion();
|
||||||
|
}
|
|
@ -76,6 +76,7 @@ public class JpaClientMapKeycloakTransaction extends JpaKeycloakTransaction impl
|
||||||
Root<JpaClientEntity> root = query.from(JpaClientEntity.class);
|
Root<JpaClientEntity> root = query.from(JpaClientEntity.class);
|
||||||
query.select(cb.construct(JpaClientEntity.class,
|
query.select(cb.construct(JpaClientEntity.class,
|
||||||
root.get("id"),
|
root.get("id"),
|
||||||
|
root.get("version"),
|
||||||
root.get("entityVersion"),
|
root.get("entityVersion"),
|
||||||
root.get("realmId"),
|
root.get("realmId"),
|
||||||
root.get("clientId"),
|
root.get("clientId"),
|
||||||
|
|
|
@ -28,21 +28,21 @@ import org.keycloak.models.map.client.MapClientEntity;
|
||||||
import org.keycloak.models.map.client.MapClientEntityFields;
|
import org.keycloak.models.map.client.MapClientEntityFields;
|
||||||
import org.keycloak.models.map.common.EntityField;
|
import org.keycloak.models.map.common.EntityField;
|
||||||
import org.keycloak.models.map.common.delegate.DelegateProvider;
|
import org.keycloak.models.map.common.delegate.DelegateProvider;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
|
||||||
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
import org.keycloak.models.map.storage.jpa.client.entity.JpaClientEntity;
|
||||||
|
|
||||||
public class JpaClientDelegateProvider implements DelegateProvider<MapClientEntity> {
|
public class JpaClientDelegateProvider extends JpaDelegateProvider<JpaClientEntity> implements DelegateProvider<MapClientEntity> {
|
||||||
|
|
||||||
private JpaClientEntity delegate;
|
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
|
|
||||||
public JpaClientDelegateProvider(JpaClientEntity delegate, EntityManager em) {
|
public JpaClientDelegateProvider(JpaClientEntity delegate, EntityManager em) {
|
||||||
this.delegate = delegate;
|
super(delegate);
|
||||||
this.em = em;
|
this.em = em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MapClientEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapClientEntity>> field, Object... parameters) {
|
public MapClientEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapClientEntity>> field, Object... parameters) {
|
||||||
if (delegate.isMetadataInitialized()) return delegate;
|
if (getDelegate().isMetadataInitialized()) return getDelegate();
|
||||||
if (isRead) {
|
if (isRead) {
|
||||||
if (field instanceof MapClientEntityFields) {
|
if (field instanceof MapClientEntityFields) {
|
||||||
switch ((MapClientEntityFields) field) {
|
switch ((MapClientEntityFields) field) {
|
||||||
|
@ -51,26 +51,26 @@ public class JpaClientDelegateProvider implements DelegateProvider<MapClientEnti
|
||||||
case CLIENT_ID:
|
case CLIENT_ID:
|
||||||
case PROTOCOL:
|
case PROTOCOL:
|
||||||
case ENABLED:
|
case ENABLED:
|
||||||
return delegate;
|
return getDelegate();
|
||||||
|
|
||||||
case ATTRIBUTES:
|
case ATTRIBUTES:
|
||||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
CriteriaQuery<JpaClientEntity> query = cb.createQuery(JpaClientEntity.class);
|
CriteriaQuery<JpaClientEntity> query = cb.createQuery(JpaClientEntity.class);
|
||||||
Root<JpaClientEntity> root = query.from(JpaClientEntity.class);
|
Root<JpaClientEntity> root = query.from(JpaClientEntity.class);
|
||||||
root.fetch("attributes", JoinType.INNER);
|
root.fetch("attributes", JoinType.INNER);
|
||||||
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(delegate.getId())));
|
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId())));
|
||||||
|
|
||||||
delegate = em.createQuery(query).getSingleResult();
|
setDelegate(em.createQuery(query).getSingleResult());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
delegate = em.find(JpaClientEntity.class, UUID.fromString(delegate.getId()));
|
setDelegate(em.find(JpaClientEntity.class, UUID.fromString(getDelegate().getId())));
|
||||||
}
|
}
|
||||||
} else throw new IllegalStateException("Not a valid client field: " + field);
|
} else throw new IllegalStateException("Not a valid client field: " + field);
|
||||||
} else {
|
} else {
|
||||||
delegate = em.find(JpaClientEntity.class, UUID.fromString(delegate.getId()));
|
setDelegate(em.find(JpaClientEntity.class, UUID.fromString(getDelegate().getId())));
|
||||||
}
|
}
|
||||||
return delegate;
|
return getDelegate();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,8 @@ import org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity;
|
||||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||||
import org.keycloak.models.map.common.DeepCloner;
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_CLIENT;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +62,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||||
public class JpaClientEntity extends AbstractClientEntity implements Serializable {
|
public class JpaClientEntity extends AbstractClientEntity implements Serializable, JpaRootEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column
|
@Column
|
||||||
|
@ -113,9 +115,10 @@ public class JpaClientEntity extends AbstractClientEntity implements Serializabl
|
||||||
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
||||||
* It is used to select client without metadata(json) field.
|
* It is used to select client without metadata(json) field.
|
||||||
*/
|
*/
|
||||||
public JpaClientEntity(UUID id, Integer entityVersion, String realmId, String clientId,
|
public JpaClientEntity(UUID id, int version, Integer entityVersion, String realmId, String clientId,
|
||||||
String protocol, Boolean enabled) {
|
String protocol, Boolean enabled) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.version = version;
|
||||||
this.entityVersion = entityVersion;
|
this.entityVersion = entityVersion;
|
||||||
this.realmId = realmId;
|
this.realmId = realmId;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
|
@ -148,6 +151,7 @@ public class JpaClientEntity extends AbstractClientEntity implements Serializabl
|
||||||
metadata.setEntityVersion(entityVersion);
|
metadata.setEntityVersion(entityVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getVersion() {
|
public int getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ public class JpaRoleMapKeycloakTransaction extends JpaKeycloakTransaction implem
|
||||||
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
||||||
query.select(cb.construct(JpaRoleEntity.class,
|
query.select(cb.construct(JpaRoleEntity.class,
|
||||||
root.get("id"),
|
root.get("id"),
|
||||||
|
root.get("version"),
|
||||||
root.get("entityVersion"),
|
root.get("entityVersion"),
|
||||||
root.get("realmId"),
|
root.get("realmId"),
|
||||||
root.get("clientId"),
|
root.get("clientId"),
|
||||||
|
|
|
@ -26,21 +26,21 @@ import org.keycloak.models.map.common.EntityField;
|
||||||
import org.keycloak.models.map.common.delegate.DelegateProvider;
|
import org.keycloak.models.map.common.delegate.DelegateProvider;
|
||||||
import org.keycloak.models.map.role.MapRoleEntity;
|
import org.keycloak.models.map.role.MapRoleEntity;
|
||||||
import org.keycloak.models.map.role.MapRoleEntityFields;
|
import org.keycloak.models.map.role.MapRoleEntityFields;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaDelegateProvider;
|
||||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||||
|
|
||||||
public class JpaRoleDelegateProvider implements DelegateProvider<MapRoleEntity> {
|
public class JpaRoleDelegateProvider extends JpaDelegateProvider<JpaRoleEntity> implements DelegateProvider<MapRoleEntity> {
|
||||||
|
|
||||||
private JpaRoleEntity delegate;
|
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
|
|
||||||
public JpaRoleDelegateProvider(JpaRoleEntity delegate, EntityManager em) {
|
public JpaRoleDelegateProvider(JpaRoleEntity delegate, EntityManager em) {
|
||||||
this.delegate = delegate;
|
super(delegate);
|
||||||
this.em = em;
|
this.em = em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JpaRoleEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapRoleEntity>> field, Object... parameters) {
|
public JpaRoleEntity getDelegate(boolean isRead, Enum<? extends EntityField<MapRoleEntity>> field, Object... parameters) {
|
||||||
if (delegate.isMetadataInitialized()) return delegate;
|
if (getDelegate().isMetadataInitialized()) return getDelegate();
|
||||||
if (isRead) {
|
if (isRead) {
|
||||||
if (field instanceof MapRoleEntityFields) {
|
if (field instanceof MapRoleEntityFields) {
|
||||||
switch ((MapRoleEntityFields) field) {
|
switch ((MapRoleEntityFields) field) {
|
||||||
|
@ -49,26 +49,26 @@ public class JpaRoleDelegateProvider implements DelegateProvider<MapRoleEntity>
|
||||||
case CLIENT_ID:
|
case CLIENT_ID:
|
||||||
case NAME:
|
case NAME:
|
||||||
case DESCRIPTION:
|
case DESCRIPTION:
|
||||||
return delegate;
|
return getDelegate();
|
||||||
|
|
||||||
case ATTRIBUTES:
|
case ATTRIBUTES:
|
||||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
CriteriaQuery<JpaRoleEntity> query = cb.createQuery(JpaRoleEntity.class);
|
CriteriaQuery<JpaRoleEntity> query = cb.createQuery(JpaRoleEntity.class);
|
||||||
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
Root<JpaRoleEntity> root = query.from(JpaRoleEntity.class);
|
||||||
root.fetch("attributes", JoinType.INNER);
|
root.fetch("attributes", JoinType.INNER);
|
||||||
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(delegate.getId())));
|
query.select(root).where(cb.equal(root.get("id"), UUID.fromString(getDelegate().getId())));
|
||||||
|
|
||||||
delegate = em.createQuery(query).getSingleResult();
|
setDelegate(em.createQuery(query).getSingleResult());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
delegate = em.find(JpaRoleEntity.class, UUID.fromString(delegate.getId()));
|
setDelegate(em.find(JpaRoleEntity.class, UUID.fromString(getDelegate().getId())));
|
||||||
}
|
}
|
||||||
} else throw new IllegalStateException("Not a valid role field: " + field);
|
} else throw new IllegalStateException("Not a valid role field: " + field);
|
||||||
} else {
|
} else {
|
||||||
delegate = em.find(JpaRoleEntity.class, UUID.fromString(delegate.getId()));
|
setDelegate(em.find(JpaRoleEntity.class, UUID.fromString(getDelegate().getId())));
|
||||||
}
|
}
|
||||||
return delegate;
|
return getDelegate();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,8 @@ import org.hibernate.annotations.TypeDefs;
|
||||||
import org.keycloak.models.map.common.DeepCloner;
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
import org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity;
|
import org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
import static org.keycloak.models.map.storage.jpa.Constants.SUPPORTED_VERSION_ROLE;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,7 +55,7 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "role", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId", "name"})})
|
@Table(name = "role", uniqueConstraints = {@UniqueConstraint(columnNames = {"realmId", "clientId", "name"})})
|
||||||
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||||
public class JpaRoleEntity extends AbstractRoleEntity implements Serializable {
|
public class JpaRoleEntity extends AbstractRoleEntity implements Serializable, JpaRootEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column
|
@Column
|
||||||
|
@ -106,8 +108,9 @@ public class JpaRoleEntity extends AbstractRoleEntity implements Serializable {
|
||||||
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
* Used by hibernate when calling cb.construct from read(QueryParameters) method.
|
||||||
* It is used to select role without metadata(json) field.
|
* It is used to select role without metadata(json) field.
|
||||||
*/
|
*/
|
||||||
public JpaRoleEntity(UUID id, Integer entityVersion, String realmId, String clientId, String name, String description) {
|
public JpaRoleEntity(UUID id, int version, Integer entityVersion, String realmId, String clientId, String name, String description) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.version = version;
|
||||||
this.entityVersion = entityVersion;
|
this.entityVersion = entityVersion;
|
||||||
this.realmId = realmId;
|
this.realmId = realmId;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
|
@ -140,6 +143,7 @@ public class JpaRoleEntity extends AbstractRoleEntity implements Serializable {
|
||||||
metadata.setEntityVersion(entityVersion);
|
metadata.setEntityVersion(entityVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getVersion() {
|
public int getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.models.delegate;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelIllegalStateException;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -83,7 +84,7 @@ public class ClientModelLazyDelegate implements ClientModel {
|
||||||
}
|
}
|
||||||
ClientModel ref = delegate.getReference();
|
ClientModel ref = delegate.getReference();
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
throw new IllegalStateException("Invalid delegate obtained");
|
throw new ModelIllegalStateException("Invalid delegate obtained");
|
||||||
}
|
}
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.model.PermissionTicket;
|
import org.keycloak.authorization.model.PermissionTicket;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
@ -46,8 +47,10 @@ import java.util.HashSet;
|
||||||
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.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@ -101,6 +104,7 @@ public class ModelToRepresentation {
|
||||||
REALM_EXCLUDED_ATTRIBUTES.add(Constants.CLIENT_PROFILES);
|
REALM_EXCLUDED_ATTRIBUTES.add(Constants.CLIENT_PROFILES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(ModelToRepresentation.class);
|
||||||
|
|
||||||
public static void buildGroupPath(StringBuilder sb, GroupModel group) {
|
public static void buildGroupPath(StringBuilder sb, GroupModel group) {
|
||||||
if (group.getParent() != null) {
|
if (group.getParent() != null) {
|
||||||
|
@ -554,6 +558,25 @@ public class ModelToRepresentation {
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles exceptions that occur when transforming the model to a representation and will remove
|
||||||
|
* all null objects from the stream.
|
||||||
|
*
|
||||||
|
* Entities that have been removed from the store or where a lazy loading exception occurs will not show up
|
||||||
|
* in the output stream.
|
||||||
|
*/
|
||||||
|
public static <M, R> Stream<R> filterValidRepresentations(Stream<M> models, Function<M, R> transformer) {
|
||||||
|
return models.map(m -> {
|
||||||
|
try {
|
||||||
|
return transformer.apply(m);
|
||||||
|
} catch (ModelIllegalStateException e) {
|
||||||
|
LOG.warn("unable to retrieve model information, skipping entity", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
public static CredentialRepresentation toRepresentation(UserCredentialModel cred) {
|
public static CredentialRepresentation toRepresentation(UserCredentialModel cred) {
|
||||||
CredentialRepresentation rep = new CredentialRepresentation();
|
CredentialRepresentation rep = new CredentialRepresentation();
|
||||||
rep.setType(CredentialRepresentation.SECRET);
|
rep.setType(CredentialRepresentation.SECRET);
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when data can't be retrieved for the model.
|
||||||
|
*
|
||||||
|
* This occurs when an entity has been removed or updated in the meantime. This might wrap an optimistic lock exception
|
||||||
|
* depending on the store.
|
||||||
|
*
|
||||||
|
* Callers might use this exception to filter out entities that are in an illegal state, see
|
||||||
|
* <code>org.keycloak.models.utils.ModelToRepresentation#toRepresentation(Stream, Function)</code>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:aschwart@redhat.com">Alexander Schwartz</a>
|
||||||
|
*/
|
||||||
|
public class ModelIllegalStateException extends ModelException {
|
||||||
|
|
||||||
|
public ModelIllegalStateException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelIllegalStateException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelIllegalStateException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelIllegalStateException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,22 +17,11 @@
|
||||||
|
|
||||||
package org.keycloak.exportimport.util;
|
package org.keycloak.exportimport.util;
|
||||||
|
|
||||||
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
import com.fasterxml.jackson.core.JsonEncoding;
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
import java.io.IOException;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import java.io.OutputStream;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.util.ArrayList;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.keycloak.authorization.AuthorizationProvider;
|
import org.keycloak.authorization.AuthorizationProvider;
|
||||||
import org.keycloak.authorization.AuthorizationProviderFactory;
|
import org.keycloak.authorization.AuthorizationProviderFactory;
|
||||||
import org.keycloak.authorization.model.Policy;
|
import org.keycloak.authorization.model.Policy;
|
||||||
|
@ -71,11 +60,20 @@ import org.keycloak.representations.idm.authorization.ResourceServerRepresentati
|
||||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonEncoding;
|
import java.io.IOException;
|
||||||
import com.fasterxml.jackson.core.JsonFactory;
|
import java.io.OutputStream;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import java.util.ArrayList;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import java.util.Collection;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -103,15 +101,17 @@ public class ExportUtils {
|
||||||
.map(ClientScopeModel::getName).collect(Collectors.toList()));
|
.map(ClientScopeModel::getName).collect(Collectors.toList()));
|
||||||
|
|
||||||
// Clients
|
// Clients
|
||||||
List<ClientModel> clients = Collections.emptyList();
|
List<ClientModel> clients = new LinkedList<>();
|
||||||
|
|
||||||
if (options.isClientsIncluded()) {
|
if (options.isClientsIncluded()) {
|
||||||
clients = realm.getClientsStream()
|
// we iterate over all clients in the stream.
|
||||||
.filter(c -> { try { c.getClientId(); return true; } catch (Exception ex) { return false; } } )
|
// only those client models that can be translated into a valid client representation will be added to the client list
|
||||||
.collect(Collectors.toList());
|
// that is later used to retrieve related information about groups and roles
|
||||||
List<ClientRepresentation> clientReps = clients.stream()
|
List<ClientRepresentation> clientReps = ModelToRepresentation.filterValidRepresentations(realm.getClientsStream(), app -> {
|
||||||
.map(app -> exportClient(session, app))
|
ClientRepresentation clientRepresentation = exportClient(session, app);
|
||||||
.collect(Collectors.toList());
|
clients.add(app);
|
||||||
|
return clientRepresentation;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
rep.setClients(clientReps);
|
rep.setClients(clientReps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelIllegalStateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
@ -241,15 +242,18 @@ public class ResourceAdminManager {
|
||||||
|
|
||||||
public GlobalRequestResult logoutAll(RealmModel realm) {
|
public GlobalRequestResult logoutAll(RealmModel realm) {
|
||||||
realm.setNotBefore(Time.currentTime());
|
realm.setNotBefore(Time.currentTime());
|
||||||
Stream<ClientModel> resources = realm.getClientsStream()
|
|
||||||
.filter(c -> { try { c.getClientId(); return true; } catch (Exception ex) { return false; } } );
|
|
||||||
|
|
||||||
GlobalRequestResult finalResult = new GlobalRequestResult();
|
GlobalRequestResult finalResult = new GlobalRequestResult();
|
||||||
AtomicInteger counter = new AtomicInteger(0);
|
AtomicInteger counter = new AtomicInteger(0);
|
||||||
resources.forEach(r -> {
|
realm.getClientsStream().forEach(c -> {
|
||||||
|
try {
|
||||||
counter.getAndIncrement();
|
counter.getAndIncrement();
|
||||||
GlobalRequestResult currentResult = logoutClient(realm, r, realm.getNotBefore());
|
GlobalRequestResult currentResult = logoutClient(realm, c, realm.getNotBefore());
|
||||||
finalResult.addAll(currentResult);
|
finalResult.addAll(currentResult);
|
||||||
|
} catch (ModelIllegalStateException ex) {
|
||||||
|
// currently, GlobalRequestResult doesn't allow for information about clients that we were unable to retrieve.
|
||||||
|
logger.warn("unable to retrieve client information for logout, skipping resource", ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
logger.debugv("logging out {0} resources ", counter);
|
logger.debugv("logging out {0} resources ", counter);
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,6 @@ import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
|
@ -87,9 +86,11 @@ public class ClientsResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get clients belonging to the realm
|
* Get clients belonging to the realm.
|
||||||
*
|
*
|
||||||
* Returns a list of clients belonging to the realm
|
* If a client can't be retrieved from the storage due to a problem with the underlying storage,
|
||||||
|
* it is silently removed from the returned list.
|
||||||
|
* This ensures that concurrent modifications to the list don't prevent callers from retrieving this list.
|
||||||
*
|
*
|
||||||
* @param clientId filter by clientId
|
* @param clientId filter by clientId
|
||||||
* @param viewableOnly filter clients that cannot be viewed in full by admin
|
* @param viewableOnly filter clients that cannot be viewed in full by admin
|
||||||
|
@ -131,9 +132,8 @@ public class ClientsResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<ClientRepresentation> s = clientModels
|
Stream<ClientRepresentation> s = ModelToRepresentation.filterValidRepresentations(clientModels,
|
||||||
.filter(c -> { try { c.getClientId(); return true; } catch (Exception ex) { return false; } } )
|
c -> {
|
||||||
.map(c -> {
|
|
||||||
ClientRepresentation representation = null;
|
ClientRepresentation representation = null;
|
||||||
if (canView || auth.clients().canView(c)) {
|
if (canView || auth.clients().canView(c)) {
|
||||||
representation = ModelToRepresentation.toRepresentation(c, session);
|
representation = ModelToRepresentation.toRepresentation(c, session);
|
||||||
|
@ -146,8 +146,7 @@ public class ClientsResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
return representation;
|
return representation;
|
||||||
})
|
});
|
||||||
.filter(Objects::nonNull);
|
|
||||||
|
|
||||||
if (!canView) {
|
if (!canView) {
|
||||||
s = paginatedStream(s, firstResult, maxResults);
|
s = paginatedStream(s, firstResult, maxResults);
|
||||||
|
|
Loading…
Reference in a new issue