parent
4b20e90292
commit
007fa1f374
20 changed files with 639 additions and 12 deletions
|
@ -64,18 +64,21 @@ public class HotRodSingleUseObjectEntity extends AbstractHotRodEntity {
|
||||||
public String id;
|
public String id;
|
||||||
|
|
||||||
@ProtoField(number = 3)
|
@ProtoField(number = 3)
|
||||||
public String userId;
|
public String objectKey;
|
||||||
|
|
||||||
@ProtoField(number = 4)
|
@ProtoField(number = 4)
|
||||||
public String actionId;
|
public String userId;
|
||||||
|
|
||||||
@ProtoField(number = 5)
|
@ProtoField(number = 5)
|
||||||
public String actionVerificationNonce;
|
public String actionId;
|
||||||
|
|
||||||
@ProtoField(number = 6)
|
@ProtoField(number = 6)
|
||||||
public Long expiration;
|
public String actionVerificationNonce;
|
||||||
|
|
||||||
@ProtoField(number = 7)
|
@ProtoField(number = 7)
|
||||||
|
public Long expiration;
|
||||||
|
|
||||||
|
@ProtoField(number = 8)
|
||||||
public Set<HotRodPair<String, String>> notes;
|
public Set<HotRodPair<String, String>> notes;
|
||||||
|
|
||||||
public static abstract class AbstractHotRodSingleUseObjectEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodSingleUseObjectEntity> implements MapSingleUseObjectEntity {
|
public static abstract class AbstractHotRodSingleUseObjectEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodSingleUseObjectEntity> implements MapSingleUseObjectEntity {
|
||||||
|
|
|
@ -32,5 +32,6 @@ public interface Constants {
|
||||||
public static final Integer CURRENT_SCHEMA_VERSION_REALM = 1;
|
public static final Integer CURRENT_SCHEMA_VERSION_REALM = 1;
|
||||||
public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1;
|
public static final Integer CURRENT_SCHEMA_VERSION_ROLE = 1;
|
||||||
public static final Integer CURRENT_SCHEMA_VERSION_ROOT_AUTH_SESSION = 1;
|
public static final Integer CURRENT_SCHEMA_VERSION_ROOT_AUTH_SESSION = 1;
|
||||||
|
public static final Integer CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT = 1;
|
||||||
public static final Integer CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE = 1;
|
public static final Integer CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.keycloak.common.util.StringPropertyReplacer;
|
||||||
import org.keycloak.component.AmphibianProviderFactory;
|
import org.keycloak.component.AmphibianProviderFactory;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.admin.AdminEvent;
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.models.ActionTokenValueModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
@ -126,6 +127,8 @@ import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentEntity;
|
||||||
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
|
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmEntity;
|
||||||
import org.keycloak.models.map.storage.jpa.role.JpaRoleMapKeycloakTransaction;
|
import org.keycloak.models.map.storage.jpa.role.JpaRoleMapKeycloakTransaction;
|
||||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.singleUseObject.JpaSingleUseObjectMapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
|
||||||
import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
|
import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
@ -184,7 +187,9 @@ public class JpaMapStorageProviderFactory implements
|
||||||
.constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new)
|
.constructor(MapWebAuthnPolicyEntity.class, MapWebAuthnPolicyEntityImpl::new)
|
||||||
//role
|
//role
|
||||||
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
|
.constructor(JpaRoleEntity.class, JpaRoleEntity::new)
|
||||||
//user login-failure
|
//single-use-object
|
||||||
|
.constructor(JpaSingleUseObjectEntity.class, JpaSingleUseObjectEntity::new)
|
||||||
|
//user-login-failure
|
||||||
.constructor(JpaUserLoginFailureEntity.class, JpaUserLoginFailureEntity::new)
|
.constructor(JpaUserLoginFailureEntity.class, JpaUserLoginFailureEntity::new)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -199,6 +204,7 @@ public class JpaMapStorageProviderFactory implements
|
||||||
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
|
MODEL_TO_TX.put(GroupModel.class, JpaGroupMapKeycloakTransaction::new);
|
||||||
MODEL_TO_TX.put(RealmModel.class, JpaRealmMapKeycloakTransaction::new);
|
MODEL_TO_TX.put(RealmModel.class, JpaRealmMapKeycloakTransaction::new);
|
||||||
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||||
|
MODEL_TO_TX.put(ActionTokenValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
|
||||||
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
|
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
|
||||||
|
|
||||||
//authz
|
//authz
|
||||||
|
|
|
@ -50,11 +50,13 @@ import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaResource
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRoleMigration;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaRootAuthenticationSessionMigration;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaScopeMigration;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaScopeMigration;
|
||||||
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaSingleUseObjectMigration;
|
||||||
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserLoginFailureMigration;
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.migration.JpaUserLoginFailureMigration;
|
||||||
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureMetadata;
|
import org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureMetadata;
|
||||||
import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentMetadata;
|
import org.keycloak.models.map.storage.jpa.realm.entity.JpaComponentMetadata;
|
||||||
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata;
|
import org.keycloak.models.map.storage.jpa.realm.entity.JpaRealmMetadata;
|
||||||
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
|
import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleMetadata;
|
||||||
|
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectMetadata;
|
||||||
|
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT;
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ADMIN_EVENT;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION;
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTHZ_PERMISSION;
|
||||||
|
@ -69,6 +71,7 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_GROUP;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM;
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_REALM;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
|
||||||
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT;
|
||||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE;
|
||||||
|
|
||||||
public class JpaEntityMigration {
|
public class JpaEntityMigration {
|
||||||
|
@ -83,6 +86,7 @@ public class JpaEntityMigration {
|
||||||
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
|
MIGRATIONS.put(JpaGroupMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_GROUP, tree, JpaGroupMigration.MIGRATORS));
|
||||||
MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS));
|
MIGRATIONS.put(JpaRealmMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_REALM, tree, JpaRealmMigration.MIGRATORS));
|
||||||
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
MIGRATIONS.put(JpaRoleMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_ROLE, tree, JpaRoleMigration.MIGRATORS));
|
||||||
|
MIGRATIONS.put(JpaSingleUseObjectMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT, tree, JpaSingleUseObjectMigration.MIGRATORS));
|
||||||
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE,tree, JpaUserLoginFailureMigration.MIGRATORS));
|
MIGRATIONS.put(JpaUserLoginFailureMetadata.class, (tree, entityVersion) -> migrateTreeTo(entityVersion, CURRENT_SCHEMA_VERSION_USER_LOGIN_FAILURE,tree, JpaUserLoginFailureMigration.MIGRATORS));
|
||||||
|
|
||||||
//authz
|
//authz
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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.hibernate.jsonb.migration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration functions for single-use objects.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||||
|
*/
|
||||||
|
public class JpaSingleUseObjectMigration {
|
||||||
|
|
||||||
|
public static final List<Function<ObjectNode, ObjectNode>> MIGRATORS = Arrays.asList(
|
||||||
|
o -> o // no migration yet
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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.singleUseObject;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
import javax.persistence.criteria.Selection;
|
||||||
|
|
||||||
|
import org.keycloak.models.ActionTokenValueModel;
|
||||||
|
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
|
||||||
|
|
||||||
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.keycloak.models.map.storage.MapKeycloakTransaction} implementation for single-use object entities.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||||
|
*/
|
||||||
|
public class JpaSingleUseObjectMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaSingleUseObjectEntity, MapSingleUseObjectEntity, ActionTokenValueModel> {
|
||||||
|
|
||||||
|
public JpaSingleUseObjectMapKeycloakTransaction(final EntityManager em) {
|
||||||
|
super(JpaSingleUseObjectEntity.class, ActionTokenValueModel.class, em);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Selection<? extends JpaSingleUseObjectEntity> selectCbConstruct(CriteriaBuilder cb, Root<JpaSingleUseObjectEntity> root) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setEntityVersion(JpaRootEntity entity) {
|
||||||
|
entity.setEntityVersion(CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JpaModelCriteriaBuilder createJpaModelCriteriaBuilder() {
|
||||||
|
return new JpaSingleUseObjectModelCriteriaBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MapSingleUseObjectEntity mapToEntityDelegate(JpaSingleUseObjectEntity original) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* 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.singleUseObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import org.keycloak.models.ActionTokenValueModel;
|
||||||
|
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
|
||||||
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link JpaModelCriteriaBuilder} implementation for single-use objects.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||||
|
*/
|
||||||
|
public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, ActionTokenValueModel, JpaSingleUseObjectModelCriteriaBuilder> {
|
||||||
|
|
||||||
|
private static final Map<String, String> FIELD_TO_JSON_PROP = new HashMap<>();
|
||||||
|
static {
|
||||||
|
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.USER_ID.getName(), "fUserId");
|
||||||
|
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.ACTION_ID.getName(), "fActionId");
|
||||||
|
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE.getName(), "fActionVerificationNonce");
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaSingleUseObjectModelCriteriaBuilder() {
|
||||||
|
super(JpaSingleUseObjectModelCriteriaBuilder::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaSingleUseObjectModelCriteriaBuilder(BiFunction<CriteriaBuilder, Root<JpaSingleUseObjectEntity>, Predicate> predicateFunc) {
|
||||||
|
super(JpaSingleUseObjectModelCriteriaBuilder::new, predicateFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JpaSingleUseObjectModelCriteriaBuilder compare(SearchableModelField<? super ActionTokenValueModel> modelField, Operator op, Object... value) {
|
||||||
|
switch (op) {
|
||||||
|
case EQ:
|
||||||
|
if (modelField == ActionTokenValueModel.SearchableFields.USER_ID ||
|
||||||
|
modelField == ActionTokenValueModel.SearchableFields.ACTION_ID ||
|
||||||
|
modelField == ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE) {
|
||||||
|
|
||||||
|
validateValue(value, modelField, op, String.class);
|
||||||
|
|
||||||
|
return new JpaSingleUseObjectModelCriteriaBuilder((cb, root) ->
|
||||||
|
cb.equal(cb.function("->>", String.class, root.get("metadata"),
|
||||||
|
cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))), value[0])
|
||||||
|
);
|
||||||
|
} else if(modelField == ActionTokenValueModel.SearchableFields.OBJECT_KEY) {
|
||||||
|
validateValue(value, modelField, op, String.class);
|
||||||
|
return new JpaSingleUseObjectModelCriteriaBuilder((cb, root) ->
|
||||||
|
cb.equal(root.get(modelField.getName()), value[0])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new CriterionNotSupportedException(modelField, op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
/*
|
||||||
|
* 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.singleUseObject.entity;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.persistence.Basic;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Version;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
import org.hibernate.annotations.TypeDef;
|
||||||
|
import org.hibernate.annotations.TypeDefs;
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.common.UuidValidator;
|
||||||
|
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
|
||||||
|
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
|
||||||
|
|
||||||
|
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPA {@link MapSingleUseObjectEntity} implementation. Some fields are annotated with {@code @Column(insertable = false, updatable = false)}
|
||||||
|
* to indicate that they are automatically generated from json fields. As such, these fields are non-insertable and non-updatable.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "kc_single_use_obj")
|
||||||
|
@TypeDefs({@TypeDef(name = "jsonb", typeClass = JsonbType.class)})
|
||||||
|
public class JpaSingleUseObjectEntity extends MapSingleUseObjectEntity.AbstractSingleUseObjectEntity implements JpaRootVersionedEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
//used for implicit optimistic locking
|
||||||
|
@Version
|
||||||
|
@Column
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
@Type(type = "jsonb")
|
||||||
|
@Column(columnDefinition = "jsonb")
|
||||||
|
private final JpaSingleUseObjectMetadata metadata;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private Integer entityVersion;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private String objectKey;
|
||||||
|
|
||||||
|
@Column(insertable = false, updatable = false)
|
||||||
|
@Basic(fetch = FetchType.LAZY)
|
||||||
|
private Long expiration;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "root", cascade = CascadeType.PERSIST, orphanRemoval = true)
|
||||||
|
private final Set<JpaSingleUseObjectNoteEntity> notes = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-argument constructor, used by hibernate to instantiate entities.
|
||||||
|
*/
|
||||||
|
public JpaSingleUseObjectEntity() {
|
||||||
|
this.metadata = new JpaSingleUseObjectMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaSingleUseObjectEntity(final DeepCloner cloner) {
|
||||||
|
this.metadata = new JpaSingleUseObjectMetadata(cloner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMetadataInitialized() {
|
||||||
|
return this.metadata != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getEntityVersion() {
|
||||||
|
if (this.isMetadataInitialized()) return this.metadata.getEntityVersion();
|
||||||
|
return this.entityVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEntityVersion(Integer entityVersion) {
|
||||||
|
this.metadata.setEntityVersion(entityVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id == null ? null : id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setId(String id) {
|
||||||
|
String validatedId = UuidValidator.validateAndConvert(id);
|
||||||
|
this.id = UUID.fromString(validatedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getObjectKey() {
|
||||||
|
if (this.isMetadataInitialized()) return this.metadata.getObjectKey();
|
||||||
|
return this.objectKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObjectKey(final String objectKey) {
|
||||||
|
this.metadata.setObjectKey(objectKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getCurrentSchemaVersion() {
|
||||||
|
return CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getActionId() {
|
||||||
|
return this.metadata.getActionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActionId(String actionId) {
|
||||||
|
this.metadata.setActionId(actionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getActionVerificationNonce() {
|
||||||
|
return this.metadata.getActionVerificationNonce();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActionVerificationNonce(String actionVerificationNonce) {
|
||||||
|
this.metadata.setActionVerificationNonce(actionVerificationNonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getNotes() {
|
||||||
|
return this.notes.stream()
|
||||||
|
.collect(Collectors.toMap(JpaSingleUseObjectNoteEntity::getName, JpaSingleUseObjectNoteEntity::getValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNote(String name) {
|
||||||
|
return this.notes.stream().filter(note -> Objects.equals(note.getName(), name))
|
||||||
|
.findFirst()
|
||||||
|
.map(JpaSingleUseObjectNoteEntity::getValue)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotes(Map<String, String> notes) {
|
||||||
|
this.notes.clear();
|
||||||
|
if (notes != null) {
|
||||||
|
notes.forEach(this::setNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNote(String name, String value) {
|
||||||
|
if (name != null) {
|
||||||
|
this.notes.removeIf(note -> Objects.equals(note.getName(), name));
|
||||||
|
if (value != null && !value.trim().isEmpty())
|
||||||
|
this.notes.add(new JpaSingleUseObjectNoteEntity(this, name, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUserId() {
|
||||||
|
return this.metadata.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.metadata.setUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getExpiration() {
|
||||||
|
if (this.isMetadataInitialized()) return this.metadata.getExpiration();
|
||||||
|
return this.expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExpiration(Long expiration) {
|
||||||
|
this.metadata.setExpiration(expiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof JpaSingleUseObjectEntity)) return false;
|
||||||
|
return Objects.equals(getId(), ((JpaSingleUseObjectEntity) obj).getId());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.singleUseObject.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.common.DeepCloner;
|
||||||
|
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntityImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that contains all the single-use object metadata that is written as JSON into the database.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||||
|
*/
|
||||||
|
public class JpaSingleUseObjectMetadata extends MapSingleUseObjectEntityImpl implements Serializable {
|
||||||
|
|
||||||
|
public JpaSingleUseObjectMetadata() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaSingleUseObjectMetadata(final DeepCloner cloner) {
|
||||||
|
super(cloner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer entityVersion;
|
||||||
|
|
||||||
|
public Integer getEntityVersion() {
|
||||||
|
return entityVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityVersion(Integer entityVersion) {
|
||||||
|
this.entityVersion = entityVersion;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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.singleUseObject.entity;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.storage.jpa.JpaAttributeEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPA implementation for single-use object notes. This entity represents a note and has a many-to-one relationship
|
||||||
|
* with the single-use object entity.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "kc_single_use_obj_note", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = {"fk_root", "name"})
|
||||||
|
})
|
||||||
|
public class JpaSingleUseObjectNoteEntity extends JpaAttributeEntity<JpaSingleUseObjectEntity> {
|
||||||
|
|
||||||
|
public JpaSingleUseObjectNoteEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaSingleUseObjectNoteEntity(final JpaSingleUseObjectEntity root, final String name, final String value) {
|
||||||
|
super(root, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof JpaSingleUseObjectNoteEntity)) return false;
|
||||||
|
JpaSingleUseObjectNoteEntity that = (JpaSingleUseObjectNoteEntity) obj;
|
||||||
|
return Objects.equals(getParent(), that.getParent()) &&
|
||||||
|
Objects.equals(getName(), that.getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,5 +27,6 @@ limitations under the License.
|
||||||
<include file="META-INF/jpa-groups-changelog.xml"/>
|
<include file="META-INF/jpa-groups-changelog.xml"/>
|
||||||
<include file="META-INF/jpa-realms-changelog.xml"/>
|
<include file="META-INF/jpa-realms-changelog.xml"/>
|
||||||
<include file="META-INF/jpa-roles-changelog.xml"/>
|
<include file="META-INF/jpa-roles-changelog.xml"/>
|
||||||
|
<include file="META-INF/jpa-single-use-objects-changelog.xml"/>
|
||||||
<include file="META-INF/jpa-user-login-failures-changelog.xml"/>
|
<include file="META-INF/jpa-user-login-failures-changelog.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||||
|
<!-- format of id of changelog file names: jpa-single-use-objects-changelog-${org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT}.xml -->
|
||||||
|
<include file="META-INF/single-use-objects/jpa-single-use-objects-changelog-1.xml"/>
|
||||||
|
</databaseChangeLog>
|
|
@ -33,6 +33,9 @@
|
||||||
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class>
|
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity</class>
|
||||||
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntity</class>
|
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleCompositeEntity</class>
|
||||||
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class>
|
<class>org.keycloak.models.map.storage.jpa.role.entity.JpaRoleAttributeEntity</class>
|
||||||
|
<!--sinle-use-objects-->
|
||||||
|
<class>org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity</class>
|
||||||
|
<class>org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectNoteEntity</class>
|
||||||
<!--user-login-failures-->
|
<!--user-login-failures-->
|
||||||
<class>org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity</class>
|
<class>org.keycloak.models.map.storage.jpa.loginFailure.entity.JpaUserLoginFailureEntity</class>
|
||||||
</persistence-unit>
|
</persistence-unit>
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
|
||||||
|
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
|
||||||
|
|
||||||
|
<changeSet author="keycloak" id="single-use-object-1">
|
||||||
|
|
||||||
|
<createTable tableName="kc_single_use_obj">
|
||||||
|
<column name="id" type="UUID">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="version" type="INTEGER" defaultValueNumeric="0">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="metadata" type="json"/>
|
||||||
|
</createTable>
|
||||||
|
<ext:addGeneratedColumn tableName="kc_single_use_obj">
|
||||||
|
<ext:column name="entityversion" type="INTEGER" jsonColumn="metadata" jsonProperty="entityVersion"/>
|
||||||
|
<ext:column name="objectkey" type="VARCHAR(255)" jsonColumn="metadata" jsonProperty="fObjectKey"/>
|
||||||
|
<ext:column name="expiration" type="BIGINT" jsonColumn="metadata" jsonProperty="fExpiration"/>
|
||||||
|
</ext:addGeneratedColumn>
|
||||||
|
<createIndex tableName="kc_single_use_obj" indexName="single_use_obj_entityVersion">
|
||||||
|
<column name="entityversion"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex tableName="kc_single_use_obj" indexName="single_use_obj_objectKey">
|
||||||
|
<column name="objectkey"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex tableName="kc_single_use_obj" indexName="single_use_obj_expiration">
|
||||||
|
<column name="expiration"/>
|
||||||
|
</createIndex>
|
||||||
|
<ext:createJsonIndex tableName="kc_single_use_obj" indexName="kc_single_use_obj_nonce">
|
||||||
|
<ext:column jsonColumn="metadata" jsonProperty="fActionVerificationNonce"/>
|
||||||
|
</ext:createJsonIndex>
|
||||||
|
|
||||||
|
<createTable tableName="kc_single_use_obj_note">
|
||||||
|
<column name="id" type="UUID">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="fk_root" type="UUID">
|
||||||
|
<constraints foreignKeyName="single_use_obj_note_fk_root_fkey" references="kc_single_use_obj(id)" deleteCascade="true"/>
|
||||||
|
</column>
|
||||||
|
<column name="name" type="VARCHAR(255)"/>
|
||||||
|
<column name="value" type="TEXT"/>
|
||||||
|
</createTable>
|
||||||
|
<createIndex tableName="kc_single_use_obj_note" indexName="single_use_obj_note_fk_root">
|
||||||
|
<column name="fk_root"/>
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
|
@ -54,6 +54,9 @@ public interface MapSingleUseObjectEntity extends AbstractEntity, UpdatableEntit
|
||||||
String getUserId();
|
String getUserId();
|
||||||
void setUserId(String userId);
|
void setUserId(String userId);
|
||||||
|
|
||||||
|
String getObjectKey();
|
||||||
|
void setObjectKey(String objectKey);
|
||||||
|
|
||||||
String getActionId();
|
String getActionId();
|
||||||
void setActionId(String actionId);
|
void setActionId(String actionId);
|
||||||
|
|
||||||
|
|
|
@ -142,11 +142,11 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
||||||
MapSingleUseObjectEntity singleUseEntity = getWithExpiration(key);
|
MapSingleUseObjectEntity singleUseEntity = getWithExpiration(key);
|
||||||
|
|
||||||
if (singleUseEntity != null) {
|
if (singleUseEntity != null) {
|
||||||
throw new ModelDuplicateException("Single-use object entity exists: " + singleUseEntity.getId());
|
throw new ModelDuplicateException("Single-use object entity exists: " + singleUseEntity.getObjectKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
singleUseEntity = new MapSingleUseObjectEntityImpl();
|
singleUseEntity = new MapSingleUseObjectEntityImpl();
|
||||||
singleUseEntity.setId(key);
|
singleUseEntity.setObjectKey(key);
|
||||||
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds));
|
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds));
|
||||||
singleUseEntity.setNotes(notes);
|
singleUseEntity.setNotes(notes);
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
||||||
|
|
||||||
if (singleUseEntity != null) {
|
if (singleUseEntity != null) {
|
||||||
Map<String, String> notes = singleUseEntity.getNotes();
|
Map<String, String> notes = singleUseEntity.getNotes();
|
||||||
if (actionTokenStoreTx.delete(key)) {
|
if (actionTokenStoreTx.delete(singleUseEntity.getId())) {
|
||||||
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
|
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
singleUseEntity = new MapSingleUseObjectEntityImpl();
|
singleUseEntity = new MapSingleUseObjectEntityImpl();
|
||||||
singleUseEntity.setId(key);
|
singleUseEntity.setObjectKey(key);
|
||||||
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds));
|
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds));
|
||||||
|
|
||||||
actionTokenStoreTx.create(singleUseEntity);
|
actionTokenStoreTx.create(singleUseEntity);
|
||||||
|
@ -228,10 +228,13 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
||||||
}
|
}
|
||||||
|
|
||||||
private MapSingleUseObjectEntity getWithExpiration(String key) {
|
private MapSingleUseObjectEntity getWithExpiration(String key) {
|
||||||
MapSingleUseObjectEntity singleUseEntity = actionTokenStoreTx.read(key);
|
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
|
||||||
|
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key);
|
||||||
|
|
||||||
|
MapSingleUseObjectEntity singleUseEntity = actionTokenStoreTx.read(withCriteria(mcb)).findFirst().orElse(null);
|
||||||
if (singleUseEntity != null) {
|
if (singleUseEntity != null) {
|
||||||
if (isExpired(singleUseEntity, false)) {
|
if (isExpired(singleUseEntity, false)) {
|
||||||
actionTokenStoreTx.delete(key);
|
actionTokenStoreTx.delete(singleUseEntity.getId());
|
||||||
} else {
|
} else {
|
||||||
return singleUseEntity;
|
return singleUseEntity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,6 +230,7 @@ public class MapFieldPredicates {
|
||||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.USER_ID, MapSingleUseObjectEntity::getUserId);
|
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.USER_ID, MapSingleUseObjectEntity::getUserId);
|
||||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_ID, MapSingleUseObjectEntity::getActionId);
|
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_ID, MapSingleUseObjectEntity::getActionId);
|
||||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, MapSingleUseObjectEntity::getActionVerificationNonce);
|
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, MapSingleUseObjectEntity::getActionVerificationNonce);
|
||||||
|
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|
|
@ -28,6 +28,7 @@ public interface ActionTokenValueModel {
|
||||||
|
|
||||||
class SearchableFields {
|
class SearchableFields {
|
||||||
public static final SearchableModelField<ActionTokenValueModel> ID = new SearchableModelField<>("id", String.class);
|
public static final SearchableModelField<ActionTokenValueModel> ID = new SearchableModelField<>("id", String.class);
|
||||||
|
public static final SearchableModelField<ActionTokenValueModel> OBJECT_KEY = new SearchableModelField<>("objectKey", String.class);
|
||||||
public static final SearchableModelField<ActionTokenValueModel> USER_ID = new SearchableModelField<>("userId", String.class);
|
public static final SearchableModelField<ActionTokenValueModel> USER_ID = new SearchableModelField<>("userId", String.class);
|
||||||
public static final SearchableModelField<ActionTokenValueModel> ACTION_ID = new SearchableModelField<>("actionId", String.class);
|
public static final SearchableModelField<ActionTokenValueModel> ACTION_ID = new SearchableModelField<>("actionId", String.class);
|
||||||
public static final SearchableModelField<ActionTokenValueModel> ACTION_VERIFICATION_NONCE = new SearchableModelField<>("actionVerificationNonce", String.class);
|
public static final SearchableModelField<ActionTokenValueModel> ACTION_VERIFICATION_NONCE = new SearchableModelField<>("actionVerificationNonce", String.class);
|
||||||
|
|
|
@ -897,6 +897,7 @@
|
||||||
<keycloak.loginFailure.map.storage.provider>jpa</keycloak.loginFailure.map.storage.provider>
|
<keycloak.loginFailure.map.storage.provider>jpa</keycloak.loginFailure.map.storage.provider>
|
||||||
<keycloak.realm.map.storage.provider>jpa</keycloak.realm.map.storage.provider>
|
<keycloak.realm.map.storage.provider>jpa</keycloak.realm.map.storage.provider>
|
||||||
<keycloak.role.map.storage.provider>jpa</keycloak.role.map.storage.provider>
|
<keycloak.role.map.storage.provider>jpa</keycloak.role.map.storage.provider>
|
||||||
|
<keycloak.singleUseObject.map.storage.provider>jpa</keycloak.singleUseObject.map.storage.provider>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
|
@ -98,7 +98,7 @@ public class JpaMapStorage extends KeycloakModelParameters {
|
||||||
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-user-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-user-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.config("storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
.config("storage-client-sessions.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi(EventStoreSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID)
|
.spi(EventStoreSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
|
Loading…
Reference in a new issue