From f36922762590bea8cfa559641b45c87ed5d4772d Mon Sep 17 00:00:00 2001 From: klaraf755 <80590912+klaraf755@users.noreply.github.com> Date: Thu, 12 Sep 2024 07:30:10 +0200 Subject: [PATCH 01/12] Clean up code related to filtering (#856) --- pom.xml | 4 +- .../core/api/ExceptionHandlingAdvice.java | 11 + .../api/web/CertificateControllerImpl.java | 12 +- .../SecurityFilterRepositoryImpl.java | 14 +- .../core/enums/SearchFieldNameEnum.java | 128 ---- .../evaluator/CertificateRuleEvaluator.java | 6 +- .../core/evaluator/RuleEvaluator.java | 46 +- .../CertificateEventHistoryService.java | 3 - .../core/service/CertificateService.java | 4 +- .../core/service/SearchService.java | 23 - .../service/impl/AuditLogServiceImpl.java | 4 +- .../CertificateEventHistoryServiceImpl.java | 15 - .../service/impl/CertificateServiceImpl.java | 148 +--- .../impl/CryptographicKeyServiceImpl.java | 25 +- .../service/impl/DiscoveryServiceImpl.java | 17 +- .../impl/EntityInstanceServiceImpl.java | 8 +- .../service/impl/LocationServiceImpl.java | 16 +- .../service/impl/ResourceServiceImpl.java | 16 +- .../service/impl/SchedulerServiceImpl.java | 16 +- .../core/service/impl/SearchServiceImpl.java | 296 -------- .../core/util/FilterPredicatesBuilder.java | 87 +++ .../czertainly/core/util/SearchHelper.java | 20 +- .../converter/Sql2PredicateConverter.java | 464 ------------ .../core/evaluator/RuleEvaluatorTest.java | 45 +- .../search/DiscoveryHistorySearchTest.java | 28 +- .../core/search/EntityInstanceSearchTest.java | 8 +- .../core/search/LocationsSearchTest.java | 12 +- .../core/service/CertificateServiceTest.java | 3 +- .../util/FilterPredicatesBuilderTest.java | 684 +++++++++++++++++- .../converter/Sql2PredicateConverterTest.java | 383 ---------- 30 files changed, 948 insertions(+), 1598 deletions(-) delete mode 100644 src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java delete mode 100644 src/main/java/com/czertainly/core/service/SearchService.java delete mode 100644 src/main/java/com/czertainly/core/service/impl/SearchServiceImpl.java delete mode 100644 src/main/java/com/czertainly/core/util/converter/Sql2PredicateConverter.java delete mode 100644 src/test/java/com/czertainly/core/util/converter/Sql2PredicateConverterTest.java diff --git a/pom.xml b/pom.xml index 4166b7fca..74da60719 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ core - 2.13.0 + 2.13.1-SNAPSHOT CZERTAINLY-Core @@ -49,7 +49,7 @@ com.czertainly interfaces - 2.13.0 + 2.13.1-SNAPSHOT diff --git a/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java b/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java index b8463b107..352722777 100644 --- a/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java +++ b/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java @@ -458,6 +458,17 @@ public ErrorMessageDto handleCertificateRequestException(CertificateRequestExcep return ErrorMessageDto.getInstance(ex.getMessage() + cause); } + /** + * Handler for {@link NotSupportedException}. + * + * @return {@link ErrorMessageDto} + */ + @ExceptionHandler(NotSupportedException.class) + @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) + public ErrorMessageDto handleTokenInstanceException(NotSupportedException ex) { + LOG.debug("HTTP 501: {}", ex.getMessage()); + return ErrorMessageDto.getInstance(ex.getMessage()); + } /** * Handler for {@link Exception}. diff --git a/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java b/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java index c243a72ff..1cc2caf4a 100644 --- a/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java +++ b/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java @@ -81,7 +81,10 @@ public void updateCertificateObjects(String uuid, CertificateUpdateObjectsDto re } @Override - public void bulkUpdateCertificateObjects(MultipleCertificateObjectUpdateDto request) throws NotFoundException { + public void bulkUpdateCertificateObjects(MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { + if (request.getFilters() != null && !request.getFilters().isEmpty() && (request.getCertificateUuids() == null || request.getCertificateUuids().isEmpty())) { + throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); + } certificateService.bulkUpdateCertificateObjects(SecurityFilter.create(), request); } @@ -100,9 +103,12 @@ public ResponseEntity upload(@RequestBody UploadCertificateRequestDto r } @Override - public BulkOperationResponse bulkDeleteCertificate(@RequestBody RemoveCertificateDto request) throws NotFoundException { - certificateService.bulkDeleteCertificate(SecurityFilter.create(), request); + public BulkOperationResponse bulkDeleteCertificate(@RequestBody RemoveCertificateDto request) throws NotFoundException, NotSupportedException { BulkOperationResponse response = new BulkOperationResponse(); + if (request.getFilters() != null && !request.getFilters().isEmpty() && (request.getUuids() == null || request.getUuids().isEmpty())) { + throw new NotSupportedException("Bulk delete of certificates by filters is not supported."); + } + certificateService.bulkDeleteCertificate(SecurityFilter.create(), request); response.setMessage("Initiated bulk delete Certificates. Please refresh after some time"); response.setStatus(BulkOperationStatus.SUCCESS); return response; diff --git a/src/main/java/com/czertainly/core/dao/repository/SecurityFilterRepositoryImpl.java b/src/main/java/com/czertainly/core/dao/repository/SecurityFilterRepositoryImpl.java index 1b350c838..c76c7cc14 100644 --- a/src/main/java/com/czertainly/core/dao/repository/SecurityFilterRepositoryImpl.java +++ b/src/main/java/com/czertainly/core/dao/repository/SecurityFilterRepositoryImpl.java @@ -3,14 +3,14 @@ import com.czertainly.api.exception.ValidationError; import com.czertainly.api.exception.ValidationException; import com.czertainly.api.model.common.NameAndUuidDto; -import com.czertainly.core.dao.entity.*; import com.czertainly.core.dao.AggregateResultDto; +import com.czertainly.core.dao.entity.CryptographicKeyItem; import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.security.authz.SecurityResourceFilter; import com.czertainly.core.util.AuthHelper; -import com.czertainly.core.util.converter.Sql2PredicateConverter; +import com.czertainly.core.util.FilterPredicatesBuilder; import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; import jakarta.persistence.criteria.*; @@ -241,8 +241,8 @@ private List getPredicates(SecurityFilter filter, TriFunction try { NameAndUuidDto userInformation = AuthHelper.getUserIdentification(); String ownerAttributePath = root.getJavaType().equals(CryptographicKeyItem.class) ? "cryptographicKey.owner" : "owner"; - Join fromOwner = Sql2PredicateConverter.prepareJoin(root, ownerAttributePath); - combinedObjectAccessPredicates.add(cb.equal(Sql2PredicateConverter.prepareExpression(fromOwner, "ownerUsername"), userInformation.getName())); + Join fromOwner = FilterPredicatesBuilder.prepareJoin(root, ownerAttributePath); + combinedObjectAccessPredicates.add(cb.equal(FilterPredicatesBuilder.prepareExpression(fromOwner, "ownerUsername"), userInformation.getName())); } catch (ValidationException e) { // cannot apply filter predicate for anonymous user } @@ -266,14 +266,14 @@ private Predicate getPredicateBySecurityResourceFilter(Root root, SecurityRes if (resourceFilter != null) { From from = root; if (attributeName.contains(".")) { - from = Sql2PredicateConverter.prepareJoin(root, attributeName.substring(0, attributeName.lastIndexOf("."))); + from = FilterPredicatesBuilder.prepareJoin(root, attributeName.substring(0, attributeName.lastIndexOf("."))); attributeName = attributeName.substring(attributeName.lastIndexOf(".") + 1); } if (resourceFilter.areOnlySpecificObjectsAllowed()) { - predicate = Sql2PredicateConverter.prepareExpression(from, attributeName).in(resourceFilter.getAllowedObjects()); + predicate = FilterPredicatesBuilder.prepareExpression(from, attributeName).in(resourceFilter.getAllowedObjects()); } else { if (!resourceFilter.getForbiddenObjects().isEmpty()) { - predicate = Sql2PredicateConverter.prepareExpression(from, attributeName).in(resourceFilter.getForbiddenObjects()).not(); + predicate = FilterPredicatesBuilder.prepareExpression(from, attributeName).in(resourceFilter.getForbiddenObjects()).not(); } } } diff --git a/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java b/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java deleted file mode 100644 index c75a00d74..000000000 --- a/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.czertainly.core.enums; - -import com.czertainly.api.model.core.auth.Resource; -import com.czertainly.api.model.core.search.SearchableFields; - -import java.util.Arrays; -import java.util.List; - -public enum SearchFieldNameEnum { - - // Certificate - COMMON_NAME(SearchableFields.COMMON_NAME, "Common Name", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - SERIAL_NUMBER_LABEL(SearchableFields.SERIAL_NUMBER, "Serial Number", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - RA_PROFILE(SearchableFields.RA_PROFILE_NAME, "RA Profile", SearchFieldTypeEnum.LIST, true, Resource.CERTIFICATE, Resource.RA_PROFILE), - CERTIFICATE_STATE(SearchableFields.CERTIFICATE_STATE, "State", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - CERTIFICATE_VALIDATION_STATUS(SearchableFields.CERTIFICATE_VALIDATION_STATUS, "Validation status", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - GROUP(SearchableFields.GROUP_NAME, "Groups", SearchFieldTypeEnum.LIST, true, Resource.CERTIFICATE, Resource.GROUP), - CERT_LOCATION_NAME(SearchableFields.CERT_LOCATION_NAME, "Locations", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, Resource.LOCATION), - OWNER(SearchableFields.OWNER, "Owner", SearchFieldTypeEnum.LIST, true, Resource.CERTIFICATE, Resource.USER), - ISSUER_COMMON_NAME(SearchableFields.ISSUER_COMMON_NAME, "Issuer Common Name", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - SIGNATURE_ALGORITHM(SearchableFields.SIGNATURE_ALGORITHM, "Signature Algorithm", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - FINGERPRINT(SearchableFields.FINGERPRINT, "Fingerprint", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - EXPIRES(SearchableFields.NOT_AFTER, "Expires At", SearchFieldTypeEnum.DATE, false, Resource.CERTIFICATE, null), - NOT_BEFORE(SearchableFields.NOT_BEFORE, "Valid From", SearchFieldTypeEnum.DATE, false, Resource.CERTIFICATE, null), - PUBLIC_KEY_ALGORITHM(SearchableFields.PUBLIC_KEY_ALGORITHM, "Public Key Algorithm", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - KEY_SIZE(SearchableFields.KEY_SIZE, "Key Size", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - KEY_USAGE(SearchableFields.KEY_USAGE, "Key Usage", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - BASIC_CONSTRAINTS(SearchableFields.BASIC_CONSTRAINTS, "Basic Constraints", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - SUBJECT_ALTERNATIVE(SearchableFields.SUBJECT_ALTERNATIVE_NAMES, "Subject Alternative Name", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - SUBJECT_DN(SearchableFields.SUBJECTDN, "Subject DN", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - ISSUER_DN(SearchableFields.ISSUERDN, "Issuer DN", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - ISSUER_SERIAL_NUMBER(SearchableFields.ISSUER_SERIAL_NUMBER, "Issuer Serial Number", SearchFieldTypeEnum.STRING, false, Resource.CERTIFICATE, null), - OCSP_VALIDATION(SearchableFields.OCSP_VALIDATION, "OCSP Validation", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - CRL_VALIDATION(SearchableFields.CRL_VALIDATION, "CRL Validation", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - SIGNATURE_VALIDATION(SearchableFields.SIGNATURE_VALIDATION, "Signature Validation", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - COMPLIANCE_STATUS(SearchableFields.COMPLIANCE_STATUS, "Compliance Status", SearchFieldTypeEnum.LIST, false, Resource.CERTIFICATE, null), - PRIVATE_KEY(SearchableFields.PRIVATE_KEY, "Has private key", SearchFieldTypeEnum.BOOLEAN, false, Resource.CERTIFICATE, null), - TRUSTED_CA(SearchableFields.TRUSTED_CA, "Trusted CA", SearchFieldTypeEnum.BOOLEAN, false, Resource.CERTIFICATE, null), - - // Cryptographic Key - CKI_NAME(SearchableFields.CKI_NAME, "Name", SearchFieldTypeEnum.STRING, false, Resource.CRYPTOGRAPHIC_KEY, null), - KEY_TYPE(SearchableFields.CKI_TYPE, "Key type", SearchFieldTypeEnum.LIST, false, Resource.CRYPTOGRAPHIC_KEY, null), - KEY_FORMAT(SearchableFields.CKI_FORMAT, "Key format", SearchFieldTypeEnum.LIST, false, Resource.CRYPTOGRAPHIC_KEY, null), - KEY_STATE(SearchableFields.CKI_STATE, "State", SearchFieldTypeEnum.LIST, true, Resource.CRYPTOGRAPHIC_KEY, null), - KEY_CRYPTOGRAPHIC_ALGORITHM(SearchableFields.CKI_CRYPTOGRAPHIC_ALGORITHM, "Cryptographic algorithm", SearchFieldTypeEnum.LIST, false, Resource.CRYPTOGRAPHIC_KEY, null), - KEY_TOKEN_PROFILE(SearchableFields.CK_TOKEN_PROFILE, "Token profile", SearchFieldTypeEnum.LIST, false, Resource.CRYPTOGRAPHIC_KEY, Resource.TOKEN_PROFILE), - KEY_TOKEN_INSTANCE_LABEL(SearchableFields.CK_TOKEN_INSTANCE, "Token instance", SearchFieldTypeEnum.LIST, true, Resource.CRYPTOGRAPHIC_KEY, Resource.TOKEN), - KEY_LENGTH(SearchableFields.CKI_LENGTH, "Key Size", SearchFieldTypeEnum.NUMBER, false, Resource.CRYPTOGRAPHIC_KEY, null), - CK_GROUP(SearchableFields.CK_GROUP, "Groups", SearchFieldTypeEnum.LIST, true, Resource.CRYPTOGRAPHIC_KEY, Resource.GROUP), - CK_OWNER(SearchableFields.CK_OWNER, "Owner", SearchFieldTypeEnum.LIST, true, Resource.CRYPTOGRAPHIC_KEY, Resource.USER), - CK_KEY_USAGE(SearchableFields.CKI_USAGE, "Key Usage", SearchFieldTypeEnum.LIST, true, Resource.CRYPTOGRAPHIC_KEY, null), - - // Discovery - DISCOVERY_NAME(SearchableFields.DISCOVERY_NAME, "Name", SearchFieldTypeEnum.DATETIME, false, Resource.DISCOVERY, null), - DISCOVERY_START_TIME(SearchableFields.DISCOVERY_START_TIME, "Start time", SearchFieldTypeEnum.DATETIME, false, Resource.DISCOVERY, null), - DISCOVERY_END_TIME(SearchableFields.DISCOVERY_END_TIME, "End time", SearchFieldTypeEnum.DATETIME, false, Resource.DISCOVERY, null), - DISCOVERY_TOTAL_CERT_DISCOVERED(SearchableFields.DISCOVERY_TOTAL_CERT_DISCOVERED, "Total certificate discovered", SearchFieldTypeEnum.NUMBER, false, Resource.DISCOVERY, null), - DISCOVERY_CONNECTOR_NAME(SearchableFields.DISCOVERY_CONNECTOR_NAME, "Discovery provider",SearchFieldTypeEnum.LIST, false, Resource.DISCOVERY, null), - DISCOVERY_KIND(SearchableFields.DISCOVERY_KIND, "Kind",SearchFieldTypeEnum.STRING, false, Resource.DISCOVERY, null), - DISCOVERY_STATUS(SearchableFields.DISCOVERY_STATUS, "Status", SearchFieldTypeEnum.LIST, false, Resource.DISCOVERY, null), - - // Entity - ENTITY_NAME(SearchableFields.ENTITY_NAME, "Name", SearchFieldTypeEnum.STRING, false, Resource.ENTITY, null), - ENTITY_CONNECTOR_NAME(SearchableFields.ENTITY_CONNECTOR_NAME, "Entity provider", SearchFieldTypeEnum.LIST, false, Resource.ENTITY, null), - ENTITY_KIND(SearchableFields.ENTITY_KIND, "Kind", SearchFieldTypeEnum.LIST, false, Resource.ENTITY, null), - - // Location - LOCATION_NAME(SearchableFields.LOCATION_NAME, "Name", SearchFieldTypeEnum.STRING, false, Resource.LOCATION, null), - LOCATION_ENTITY_INSTANCE(SearchableFields.LOCATION_ENTITY_INSTANCE, "Entity instance", SearchFieldTypeEnum.LIST, false, Resource.LOCATION, Resource.ENTITY), - LOCATION_ENABLED(SearchableFields.LOCATION_ENABLED, "Enabled", SearchFieldTypeEnum.BOOLEAN, true, Resource.LOCATION, null), - LOCATION_SUPPORT_MULTIPLE_ENTRIES(SearchableFields.LOCATION_SUPPORT_MULTIPLE_ENTRIES, "Support multiple entries", SearchFieldTypeEnum.BOOLEAN, false, Resource.LOCATION, null), - LOCATION_SUPPORT_KEY_MANAGEMENT(SearchableFields.LOCATION_SUPPORT_KEY_MANAGEMENT, "Support key management", SearchFieldTypeEnum.BOOLEAN, false, Resource.LOCATION, null), - ; - - private final SearchableFields fieldProperty; - - private final String fieldLabel; - - private final SearchFieldTypeEnum fieldTypeEnum; - - private final boolean settable; - - private final Resource resource; - - private final Resource fieldResource; - - SearchFieldNameEnum(final SearchableFields fieldProperty, final String fieldLabel, final SearchFieldTypeEnum fieldTypeEnum, final boolean settable, final Resource resource, final Resource fieldResource) { - this.fieldProperty = fieldProperty; - this.fieldLabel = fieldLabel; - this.fieldTypeEnum = fieldTypeEnum; - this.settable = settable; - this.resource = resource; - this.fieldResource = fieldResource; - } - - public SearchableFields getFieldProperty() { - return fieldProperty; - } - - public String getFieldLabel() { - return fieldLabel; - } - - public SearchFieldTypeEnum getFieldTypeEnum() { - return fieldTypeEnum; - } - - public boolean isSettable() { - return settable; - } - - public Resource getResource() { return this.resource; } - - public Resource getFieldResource() {return this.fieldResource;} - - public static SearchFieldNameEnum getEnumBySearchableFields(final SearchableFields searchableFields) { - for (SearchFieldNameEnum searchFieldNameEnum : SearchFieldNameEnum.values()) { - if (searchFieldNameEnum.getFieldProperty().equals(searchableFields)) { - return searchFieldNameEnum; - } - } - return null; - } - - public static List getEnumsForResource(Resource resource) { - return Arrays.stream(SearchFieldNameEnum.values()).filter(searchFieldNameEnum -> searchFieldNameEnum.resource == resource).toList(); - } -} diff --git a/src/main/java/com/czertainly/core/evaluator/CertificateRuleEvaluator.java b/src/main/java/com/czertainly/core/evaluator/CertificateRuleEvaluator.java index 7d934c835..89721e492 100644 --- a/src/main/java/com/czertainly/core/evaluator/CertificateRuleEvaluator.java +++ b/src/main/java/com/czertainly/core/evaluator/CertificateRuleEvaluator.java @@ -6,9 +6,9 @@ import com.czertainly.api.exception.RuleException; import com.czertainly.api.model.core.auth.Resource; import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.workflows.ExecutionItem; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.service.CertificateService; import org.springframework.beans.factory.annotation.Autowired; @@ -37,9 +37,9 @@ public void performAction(ExecutionItem executionItem, Certificate object, Resou SecuredUUID certificateUuid = object.getSecuredUuid(); - SearchableFields searchableField; + FilterField searchableField; try { - searchableField = Enum.valueOf(SearchableFields.class, executionItem.getFieldIdentifier()); + searchableField = Enum.valueOf(FilterField.class, executionItem.getFieldIdentifier()); } catch (IllegalArgumentException e) { throw new RuleException("Field identifier '" + executionItem.getFieldIdentifier() + "' is not supported."); } diff --git a/src/main/java/com/czertainly/core/evaluator/RuleEvaluator.java b/src/main/java/com/czertainly/core/evaluator/RuleEvaluator.java index bcb64a811..9a8dd3c4c 100644 --- a/src/main/java/com/czertainly/core/evaluator/RuleEvaluator.java +++ b/src/main/java/com/czertainly/core/evaluator/RuleEvaluator.java @@ -13,14 +13,15 @@ import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; import com.czertainly.api.model.core.search.FilterFieldType; -import com.czertainly.api.model.core.search.SearchableFields; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; import com.czertainly.core.dao.entity.workflows.*; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.enums.ResourceToClass; -import com.czertainly.core.enums.SearchFieldNameEnum; import com.czertainly.core.service.TriggerService; import com.czertainly.core.util.AttributeDefinitionUtils; +import com.czertainly.core.util.FilterPredicatesBuilder; +import jakarta.persistence.metamodel.Attribute; import org.apache.commons.beanutils.PropertyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -116,37 +117,27 @@ public Boolean evaluateConditionItem(ConditionItem conditionItem, T object, Reso // First, check where from to get object value based on Field Source if (fieldSource == FilterFieldSource.PROPERTY) { Object objectValue; - SearchableFields field; + FilterField field; try { - field = Enum.valueOf(SearchableFields.class, fieldIdentifier); + field = Enum.valueOf(FilterField.class, fieldIdentifier); } catch (IllegalArgumentException e) { throw new RuleException("Field identifier '" + fieldIdentifier + "' is not supported."); } // Get value of property from the object try { - objectValue = getPropertyValue(object, field.getCode(), false); + objectValue = getPropertyValue(object, field, false); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuleException("Cannot get property " + fieldIdentifier + " from resource " + resource + "."); } - // Determine field type from field identifier using Searchable field enum - SearchFieldNameEnum propertyEnum; - try { - propertyEnum = SearchFieldNameEnum.getEnumBySearchableFields(field); - } catch (Exception e) { - throw new RuleException("Field identifier '" + fieldIdentifier + "' is not supported."); - } - if (propertyEnum == null) { - throw new RuleException("Unknown property field identifier: " + fieldIdentifier); - } - FilterFieldType fieldType = propertyEnum.getFieldTypeEnum().getFieldType(); + FilterFieldType fieldType = field.getType().getFieldType(); // Apply comparing function on value in object and value in condition, based on operator and field type, return whether the condition is satisfied try { if (!(objectValue instanceof Collection objectValues)) { return fieldTypeToOperatorActionMap.get(fieldType).get(operator).apply(objectValue, conditionValue); } for (Object item : objectValues) { - Object o = getPropertyValue(item, field.getCode(), true); + Object o = getPropertyValue(item, field, true); if (!fieldTypeToOperatorActionMap.get(fieldType).get(operator).apply(o, conditionValue)) { return false; } @@ -238,14 +229,14 @@ public void performAction(ExecutionItem executionItem, T object, Resource resour // Set a property of the object using setter, the property must be set as settable if (fieldSource == FilterFieldSource.PROPERTY) { - SearchFieldNameEnum propertyEnum = SearchFieldNameEnum.getEnumBySearchableFields(SearchableFields.fromCode(fieldIdentifier)); + FilterField propertyEnum = Enum.valueOf(FilterField.class, fieldIdentifier); if (propertyEnum == null) { throw new RuleException("Field identifier '" + fieldIdentifier + "' is not supported."); } if (!propertyEnum.isSettable()) throw new RuleException("Setting property '" + fieldIdentifier + "' is not supported."); try { - PropertyUtils.setProperty(object, propertyEnum.getFieldProperty().getCode(), actionData); + PropertyUtils.setProperty(object, propertyEnum.getFieldAttribute().getName(), actionData); } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuleException(e.getMessage()); @@ -268,29 +259,32 @@ public void performAction(ExecutionItem executionItem, T object, Resource resour } } - private Object getPropertyValue(Object object, String fieldIdentifier, boolean alreadyNested) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { - final int indexOfDot = fieldIdentifier.lastIndexOf("."); - boolean isNested = indexOfDot != -1; + private Object getPropertyValue(Object object, FilterField filterField, boolean alreadyNested) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { + boolean isNested = filterField.getJoinAttributes() != null; + String pathToProperty = FilterPredicatesBuilder.buildPathToProperty(filterField, alreadyNested); + try { if (alreadyNested) { - return PropertyUtils.getProperty(object, fieldIdentifier.substring(indexOfDot + 1)); + return PropertyUtils.getProperty(object, pathToProperty); } - return PropertyUtils.getProperty(object, fieldIdentifier); + return PropertyUtils.getProperty(object, pathToProperty); } catch (NoSuchMethodException e) { if (!isNested || alreadyNested) { throw e; } - final String parentPropertyIdentifier = fieldIdentifier.substring(0, indexOfDot); - Object tmpValue = PropertyUtils.getProperty(object, parentPropertyIdentifier); + Object tmpValue = PropertyUtils.getProperty(object, filterField.getJoinAttributes().getFirst().getName()); if (tmpValue instanceof Collection) { return tmpValue; } throw e; + } + } + private boolean getConditionEvaluationResult(ConditionItem conditionItem, T object, TriggerHistory triggerHistory, Rule rule) { try { if (!evaluateConditionItem(conditionItem, object, rule.getResource())) { diff --git a/src/main/java/com/czertainly/core/service/CertificateEventHistoryService.java b/src/main/java/com/czertainly/core/service/CertificateEventHistoryService.java index 4b26b2ad1..17e33fa21 100644 --- a/src/main/java/com/czertainly/core/service/CertificateEventHistoryService.java +++ b/src/main/java/com/czertainly/core/service/CertificateEventHistoryService.java @@ -1,11 +1,9 @@ package com.czertainly.core.service; import com.czertainly.api.exception.NotFoundException; -import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; import com.czertainly.api.model.core.certificate.CertificateEvent; import com.czertainly.api.model.core.certificate.CertificateEventHistoryDto; import com.czertainly.api.model.core.certificate.CertificateEventStatus; -import com.czertainly.api.model.core.search.SearchFieldDataDto; import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.CertificateEventHistory; @@ -31,5 +29,4 @@ public interface CertificateEventHistoryService { void addEventHistory(UUID certificateUuid, CertificateEvent event, CertificateEventStatus status, String message, String additionalInformation); - void addEventHistoryForRequest(List filters, String entity, List originalJson, CertificateEvent event, CertificateEventStatus status, String message); } diff --git a/src/main/java/com/czertainly/core/service/CertificateService.java b/src/main/java/com/czertainly/core/service/CertificateService.java index f49ae01dc..3fffc967e 100644 --- a/src/main/java/com/czertainly/core/service/CertificateService.java +++ b/src/main/java/com/czertainly/core/service/CertificateService.java @@ -85,7 +85,7 @@ public interface CertificateService extends ResourceExtensionService { List getSearchableFieldInformationByGroup(); - void bulkDeleteCertificate(SecurityFilter filter, RemoveCertificateDto request) throws NotFoundException; + void bulkDeleteCertificate(SecurityFilter filter, RemoveCertificateDto request) throws NotFoundException, NotSupportedException; /** * List all locations associated with the certificate @@ -164,7 +164,7 @@ public interface CertificateService extends ResourceExtensionService { * * @param request Request to update multiple objects */ - void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException; + void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException; /** * Function to update status of certificates by scheduled event diff --git a/src/main/java/com/czertainly/core/service/SearchService.java b/src/main/java/com/czertainly/core/service/SearchService.java deleted file mode 100644 index 13bd05989..000000000 --- a/src/main/java/com/czertainly/core/service/SearchService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.czertainly.core.service; - -import com.czertainly.api.exception.ValidationException; -import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; -import com.czertainly.api.model.client.certificate.SearchRequestDto; -import com.czertainly.api.model.core.search.DynamicSearchInternalResponse; -import com.czertainly.api.model.core.search.SearchFieldDataDto; -import com.czertainly.core.security.authz.SecurityFilter; - -import java.util.List; - -public interface SearchService { - Object completeSearchQueryExecutor(List filters, String entity, List originalJson); - DynamicSearchInternalResponse dynamicSearchQueryExecutor(SearchRequestDto searchRequestDto, String entity, List originalJson, String additionalWhereClause); - - Object customQueryExecutor(String sqlQuery); - - String getCompleteSearchQuery(List filters, String entity, String joinQuery, List originalJson, Boolean conditionOnly, Boolean nativeCode); - - String getQueryDynamicBasedOnFilter(List conditions, String entity, List originalJson, String joinQuery, Boolean conditionOnly, Boolean nativeCode, String additionalWhereClause) throws ValidationException; - - String createCriteriaBuilderString(SecurityFilter filter, Boolean addFinisher); -} diff --git a/src/main/java/com/czertainly/core/service/impl/AuditLogServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/AuditLogServiceImpl.java index c100acaea..33307ffa7 100644 --- a/src/main/java/com/czertainly/core/service/impl/AuditLogServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/AuditLogServiceImpl.java @@ -9,7 +9,7 @@ import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authz.ExternalAuthorization; import com.czertainly.core.service.AuditLogService; -import com.czertainly.core.util.converter.Sql2PredicateConverter; +import com.czertainly.core.util.FilterPredicatesBuilder; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -166,7 +166,7 @@ public ExportResultDto exportAuditLogs(AuditLogFilter filter, Sort sort) { @AuditLogged(originator = ObjectType.FE, affected = ObjectType.AUDIT_LOG, operation = OperationType.DELETE) @ExternalAuthorization(resource = Resource.AUDIT_LOG, action = ResourceAction.DELETE) public void purgeAuditLogs(AuditLogFilter filter, Sort sort) { - CriteriaDelete criteriaQueryDataObject = Sql2PredicateConverter.prepareQueryForAuditLog(filter, entityManager.getCriteriaBuilder()); + CriteriaDelete criteriaQueryDataObject = FilterPredicatesBuilder.prepareQueryForAuditLog(filter, entityManager.getCriteriaBuilder()); entityManager.createQuery(criteriaQueryDataObject).executeUpdate(); } diff --git a/src/main/java/com/czertainly/core/service/impl/CertificateEventHistoryServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CertificateEventHistoryServiceImpl.java index 60f4dd7f9..1ed2461ad 100644 --- a/src/main/java/com/czertainly/core/service/impl/CertificateEventHistoryServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/CertificateEventHistoryServiceImpl.java @@ -1,17 +1,14 @@ package com.czertainly.core.service.impl; import com.czertainly.api.exception.NotFoundException; -import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; import com.czertainly.api.model.core.certificate.CertificateEvent; import com.czertainly.api.model.core.certificate.CertificateEventHistoryDto; import com.czertainly.api.model.core.certificate.CertificateEventStatus; -import com.czertainly.api.model.core.search.SearchFieldDataDto; import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.CertificateEventHistory; import com.czertainly.core.dao.repository.CertificateEventHistoryRepository; import com.czertainly.core.dao.repository.CertificateRepository; import com.czertainly.core.service.CertificateEventHistoryService; -import com.czertainly.core.service.SearchService; import com.czertainly.core.util.MetaDefinitions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +17,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -36,8 +32,6 @@ public class CertificateEventHistoryServiceImpl implements CertificateEventHisto private CertificateRepository certificateRepository; @Autowired private CertificateEventHistoryRepository certificateEventHistoryRepository; - @Autowired - private SearchService searchService; @Override public void addEventHistory(UUID certificateUuid, CertificateEvent event, CertificateEventStatus status, String message, HashMap additionalInformation) { @@ -79,14 +73,5 @@ public void asyncSaveAllInBatch(List certificateEventHi logger.info("Inserted {} record into the database", certificateEventHistories.size()); } - @Override - @Async - public void addEventHistoryForRequest(List filters, String entity, List originalJson, CertificateEvent event, CertificateEventStatus status, String message) { - List batchHistoryOperationList = new ArrayList<>(); - for (Certificate certificate : (List) searchService.completeSearchQueryExecutor(filters, "Certificate", originalJson)) { - batchHistoryOperationList.add(getEventHistory(event, status, message, "", certificate)); - } - asyncSaveAllInBatch(batchHistoryOperationList); - } } diff --git a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java index 189d3a485..bd38290b3 100644 --- a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java @@ -33,7 +33,7 @@ import com.czertainly.core.comparator.SearchFieldDataComparator; import com.czertainly.core.dao.entity.*; import com.czertainly.core.dao.repository.*; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.event.transaction.CertificateValidationEvent; import com.czertainly.core.messaging.model.NotificationRecipient; import com.czertainly.core.messaging.producers.EventProducer; @@ -49,7 +49,6 @@ import com.czertainly.core.service.*; import com.czertainly.core.service.v2.ExtendedAttributeService; import com.czertainly.core.util.*; -import com.czertainly.core.util.converter.Sql2PredicateConverter; import com.czertainly.core.validation.certificate.ICertificateValidator; import jakarta.persistence.criteria.*; import org.apache.commons.lang3.function.TriFunction; @@ -91,7 +90,9 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.BiFunction; import java.util.stream.Collectors; @@ -142,9 +143,6 @@ public class CertificateServiceImpl implements CertificateService { @Autowired private CertificateEventHistoryService certificateEventHistoryService; - @Autowired - private SearchService searchService; - @Lazy @Autowired private LocationService locationService; @@ -374,7 +372,7 @@ private void updateTrustedCaMark(SecuredUUID uuid, Boolean trustedCa) throws Not @Override @AuditLogged(originator = ObjectType.FE, affected = ObjectType.CERTIFICATE, operation = OperationType.CHANGE) @ExternalAuthorization(resource = Resource.CERTIFICATE, action = ResourceAction.UPDATE, parentResource = Resource.RA_PROFILE, parentAction = ResourceAction.DETAIL) - public void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException { + public void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { logger.info("Bulk updating certificate objects: RA {} groups {} owner {}", request.getRaProfileUuid(), request.getGroupUuids(), request.getOwnerUuid()); setupSecurityFilter(filter); if (request.getRaProfileUuid() != null) { @@ -393,7 +391,7 @@ public void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertific @Async @ExternalAuthorization(resource = Resource.CERTIFICATE, action = ResourceAction.DELETE, parentResource = Resource.RA_PROFILE, parentAction = ResourceAction.DETAIL) @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void bulkDeleteCertificate(SecurityFilter filter, RemoveCertificateDto request) throws NotFoundException { + public void bulkDeleteCertificate(SecurityFilter filter, RemoveCertificateDto request) throws NotFoundException, NotSupportedException { setupSecurityFilter(filter); UUID loggedUserUuid = null; @@ -414,61 +412,42 @@ public void bulkDeleteCertificate(SecurityFilter filter, RemoveCertificateDto re } logger.debug("Bulk deleted {} of {} certificates.", deletedCount, request.getUuids().size()); } else { - String joins = "WHERE c.userUuid IS NULL"; - String data = searchService.createCriteriaBuilderString(filter, true); - if (!data.isEmpty()) { - joins = joins + " AND " + data; - } - - String customQuery = searchService.getQueryDynamicBasedOnFilter(request.getFilters(), "Certificate", getSearchableFieldInformation(), joins, false, false, ""); - - List certListDyn = (List) searchService.customQueryExecutor(customQuery); - - for (List certificates : partitionList(certListDyn)) { - certificateRepository.deleteAll(certificates); - } - for (List certificateContents : partitionContents(certificateContentRepository.findCertificateContentNotUsed())) { - certificateContentRepository.deleteAll(certificateContents); - } + throw new NotSupportedException("Bulk delete of certificates by filters is not supported."); } } - @Deprecated - public List getSearchableFieldInformation() { - return getSearchableFieldsMap(); - } @Override public List getSearchableFieldInformationByGroup() { final List searchFieldDataByGroupDtos = attributeEngine.getResourceSearchableFields(Resource.CERTIFICATE, false); List fields = List.of( - SearchHelper.prepareSearch(SearchFieldNameEnum.COMMON_NAME), - SearchHelper.prepareSearch(SearchFieldNameEnum.SERIAL_NUMBER_LABEL), - SearchHelper.prepareSearch(SearchFieldNameEnum.ISSUER_SERIAL_NUMBER), - SearchHelper.prepareSearch(SearchFieldNameEnum.RA_PROFILE, raProfileRepository.findAll().stream().map(RaProfile::getName).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.GROUP, groupRepository.findAll().stream().map(Group::getName).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.CERT_LOCATION_NAME, locationRepository.findAll().stream().map(Location::getName).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.OWNER, userManagementApiClient.getUsers().getData().stream().map(UserDto::getUsername).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.CERTIFICATE_STATE, Arrays.stream(CertificateState.values()).map(CertificateState::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.CERTIFICATE_VALIDATION_STATUS, Arrays.stream(CertificateValidationStatus.values()).map(CertificateValidationStatus::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.COMPLIANCE_STATUS, Arrays.stream(ComplianceStatus.values()).map(ComplianceStatus::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.ISSUER_COMMON_NAME), - SearchHelper.prepareSearch(SearchFieldNameEnum.FINGERPRINT), - SearchHelper.prepareSearch(SearchFieldNameEnum.SIGNATURE_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctSignatureAlgorithm())), - SearchHelper.prepareSearch(SearchFieldNameEnum.EXPIRES), - SearchHelper.prepareSearch(SearchFieldNameEnum.NOT_BEFORE), - SearchHelper.prepareSearch(SearchFieldNameEnum.SUBJECT_DN), - SearchHelper.prepareSearch(SearchFieldNameEnum.ISSUER_DN), - SearchHelper.prepareSearch(SearchFieldNameEnum.SUBJECT_ALTERNATIVE), - SearchHelper.prepareSearch(SearchFieldNameEnum.OCSP_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.CRL_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.SIGNATURE_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.PUBLIC_KEY_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctPublicKeyAlgorithm())), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_SIZE, new ArrayList<>(certificateRepository.findDistinctKeySize())), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_USAGE, serializedListOfStringToListOfObject(certificateRepository.findDistinctKeyUsage())), - SearchHelper.prepareSearch(SearchFieldNameEnum.PRIVATE_KEY), - SearchHelper.prepareSearch(SearchFieldNameEnum.TRUSTED_CA) + SearchHelper.prepareSearch(FilterField.COMMON_NAME), + SearchHelper.prepareSearch(FilterField.SERIAL_NUMBER), + SearchHelper.prepareSearch(FilterField.ISSUER_SERIAL_NUMBER), + SearchHelper.prepareSearch(FilterField.RA_PROFILE_NAME, raProfileRepository.findAll().stream().map(RaProfile::getName).toList()), + SearchHelper.prepareSearch(FilterField.GROUP_NAME, groupRepository.findAll().stream().map(Group::getName).toList()), + SearchHelper.prepareSearch(FilterField.CERT_LOCATION_NAME, locationRepository.findAll().stream().map(Location::getName).toList()), + SearchHelper.prepareSearch(FilterField.OWNER, userManagementApiClient.getUsers().getData().stream().map(UserDto::getUsername).toList()), + SearchHelper.prepareSearch(FilterField.CERTIFICATE_STATE, Arrays.stream(CertificateState.values()).map(CertificateState::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CERTIFICATE_VALIDATION_STATUS, Arrays.stream(CertificateValidationStatus.values()).map(CertificateValidationStatus::getCode).toList()), + SearchHelper.prepareSearch(FilterField.COMPLIANCE_STATUS, Arrays.stream(ComplianceStatus.values()).map(ComplianceStatus::getCode).toList()), + SearchHelper.prepareSearch(FilterField.ISSUER_COMMON_NAME), + SearchHelper.prepareSearch(FilterField.FINGERPRINT), + SearchHelper.prepareSearch(FilterField.SIGNATURE_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctSignatureAlgorithm())), + SearchHelper.prepareSearch(FilterField.NOT_AFTER), + SearchHelper.prepareSearch(FilterField.NOT_BEFORE), + SearchHelper.prepareSearch(FilterField.SUBJECTDN), + SearchHelper.prepareSearch(FilterField.ISSUERDN), + SearchHelper.prepareSearch(FilterField.SUBJECT_ALTERNATIVE_NAMES), + SearchHelper.prepareSearch(FilterField.OCSP_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CRL_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), + SearchHelper.prepareSearch(FilterField.SIGNATURE_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), + SearchHelper.prepareSearch(FilterField.PUBLIC_KEY_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctPublicKeyAlgorithm())), + SearchHelper.prepareSearch(FilterField.KEY_SIZE, new ArrayList<>(certificateRepository.findDistinctKeySize())), + SearchHelper.prepareSearch(FilterField.KEY_USAGE, serializedListOfStringToListOfObject(certificateRepository.findDistinctKeyUsage())), + SearchHelper.prepareSearch(FilterField.PRIVATE_KEY), + SearchHelper.prepareSearch(FilterField.TRUSTED_CA) ); fields = new ArrayList<>(fields); @@ -1387,34 +1366,6 @@ public List listCmpSigningCertificates(SecurityFilter filter) { .map(Certificate::mapToListDto).toList(); } - private String getExpiryTime(Date now, Date expiry) { - long diffInMilliseconds = expiry.getTime() - now.getTime(); - long difference = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS); - if (diffInMilliseconds <= 0) { - return "expired"; - } else if (difference < 10) { - return "10"; - } else if (difference < 20) { - return "20"; - } else if (difference < 30) { - return "30"; - } else if (difference < 60) { - return "60"; - } else if (difference < 90) { - return "90"; - } - return "More"; - } - - - @Deprecated - private List getSearchableFieldsMap() { - - final List fields = List.of(SearchHelper.prepareSearch(SearchFieldNameEnum.COMMON_NAME), SearchHelper.prepareSearch(SearchFieldNameEnum.SERIAL_NUMBER_LABEL), SearchHelper.prepareSearch(SearchFieldNameEnum.ISSUER_SERIAL_NUMBER), SearchHelper.prepareSearch(SearchFieldNameEnum.RA_PROFILE, raProfileRepository.findAll().stream().map(RaProfile::getName).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.GROUP, groupRepository.findAll().stream().map(Group::getName).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.OWNER), SearchHelper.prepareSearch(SearchFieldNameEnum.CERTIFICATE_STATE, Arrays.stream(CertificateState.values()).map(CertificateState::getCode).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.CERTIFICATE_VALIDATION_STATUS, Arrays.stream(CertificateValidationStatus.values()).map(CertificateValidationStatus::getCode).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.COMPLIANCE_STATUS, Arrays.stream(ComplianceStatus.values()).map(ComplianceStatus::getCode).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.ISSUER_COMMON_NAME), SearchHelper.prepareSearch(SearchFieldNameEnum.FINGERPRINT), SearchHelper.prepareSearch(SearchFieldNameEnum.SIGNATURE_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctSignatureAlgorithm())), SearchHelper.prepareSearch(SearchFieldNameEnum.EXPIRES), SearchHelper.prepareSearch(SearchFieldNameEnum.NOT_BEFORE), SearchHelper.prepareSearch(SearchFieldNameEnum.SUBJECT_DN), SearchHelper.prepareSearch(SearchFieldNameEnum.ISSUER_DN), SearchHelper.prepareSearch(SearchFieldNameEnum.SUBJECT_ALTERNATIVE), SearchHelper.prepareSearch(SearchFieldNameEnum.OCSP_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.CRL_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.SIGNATURE_VALIDATION, Arrays.stream((CertificateValidationStatus.values())).map(CertificateValidationStatus::getCode).toList()), SearchHelper.prepareSearch(SearchFieldNameEnum.PUBLIC_KEY_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctPublicKeyAlgorithm())), SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_SIZE, new ArrayList<>(certificateRepository.findDistinctKeySize())), SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_USAGE, serializedListOfStringToListOfObject(certificateRepository.findDistinctKeyUsage()))); - - logger.debug("Searchable Fields: {}", fields); - return fields; - } private List serializedListOfStringToListOfObject(List serializedData) { Set serSet = new LinkedHashSet<>(); @@ -1460,14 +1411,6 @@ private CertificateComplianceResultDto getCertificateComplianceResultDto(Complia return dto; } - // TODO - move to search service - private void bulkUpdateRaProfileComplianceCheck(List searchFilter) { - List certificates = (List) searchService.completeSearchQueryExecutor(searchFilter, "Certificate", getSearchableFieldInformation()); - CertificateComplianceCheckDto dto = new CertificateComplianceCheckDto(); - dto.setCertificateUuids(certificates.stream().map(Certificate::getUuid).map(UUID::toString).toList()); - checkCompliance(dto); - } - private void certificateComplianceCheck(Certificate certificate) { if (certificate.getRaProfile() != null) { try { @@ -1642,7 +1585,7 @@ public void updateOwner(SecuredUUID uuid, String ownerUuid) throws NotFoundExcep certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, "%s -> %s".formatted(currentOwnerName, newOwnerName == null ? UNDEFINED_CERTIFICATE_OBJECT_NAME : newOwnerName), ""); } - private void bulkUpdateRaProfile(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException { + private void bulkUpdateRaProfile(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { boolean removeRaProfile = request.getRaProfileUuid().isEmpty(); if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) { for (String certificateUuidString : request.getCertificateUuids()) { @@ -1657,17 +1600,7 @@ private void bulkUpdateRaProfile(SecurityFilter filter, MultipleCertificateObjec } } } else { - RaProfile raProfile = removeRaProfile ? null : raProfileRepository.findByUuid(SecuredUUID.fromString(request.getRaProfileUuid())).orElseThrow(() -> new NotFoundException(RaProfile.class, request.getRaProfileUuid())); - - String data = searchService.createCriteriaBuilderString(filter, false); - if (!data.isEmpty()) { - data = "WHERE " + data; - } - - String profileUpdateQuery = "UPDATE Certificate c SET c.raProfile = " + (removeRaProfile ? "NULL" : raProfile.getUuid()) + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", ""); - certificateRepository.bulkUpdateQuery(profileUpdateQuery); - certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.SUCCESS, "RA Profile Name: " + (removeRaProfile ? UNDEFINED_CERTIFICATE_OBJECT_NAME : raProfile.getName())); - bulkUpdateRaProfileComplianceCheck(request.getFilters()); + throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); } } @@ -1685,7 +1618,7 @@ private void bulkUpdateCertificateGroup(SecurityFilter filter, MultipleCertifica // } } - private void bulkUpdateOwner(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException { + private void bulkUpdateOwner(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { boolean removeOwner = request.getOwnerUuid().isEmpty(); String ownerUuid = null; String ownerName = null; @@ -1703,14 +1636,7 @@ private void bulkUpdateOwner(SecurityFilter filter, MultipleCertificateObjectUpd certificateRepository.saveAll(batchOperationList); certificateEventHistoryService.asyncSaveAllInBatch(batchHistoryOperationList); } else { - String data = searchService.createCriteriaBuilderString(filter, false); - if (!data.isEmpty()) { - data = "WHERE " + data; - } - String ownerUpdateQuery = "UPDATE Certificate c SET c.owner = '" + (removeOwner ? "NULL" : ownerName) + "',c.owner_uuid = '" + (removeOwner ? "NULL" : UUID.fromString(request.getOwnerUuid())) + "' " + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", ""); - certificateRepository.bulkUpdateQuery(ownerUpdateQuery); - certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, "Owner: " + (removeOwner ? UNDEFINED_CERTIFICATE_OBJECT_NAME : ownerName)); - } + throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); } } private List> partitionList(List fullList) { diff --git a/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java index 18f296dfa..5cf741afc 100644 --- a/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java @@ -30,7 +30,7 @@ import com.czertainly.core.comparator.SearchFieldDataComparator; import com.czertainly.core.dao.entity.*; import com.czertainly.core.dao.repository.*; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authn.client.UserManagementApiClient; import com.czertainly.core.security.authz.ExternalAuthorization; @@ -1199,17 +1199,18 @@ private List getSearchableFieldsMap() { final List searchFieldDataByGroupDtos = attributeEngine.getResourceSearchableFields(Resource.CRYPTOGRAPHIC_KEY, false); List fields = List.of( - SearchHelper.prepareSearch(SearchFieldNameEnum.CKI_NAME), - SearchHelper.prepareSearch(SearchFieldNameEnum.CK_GROUP, groupRepository.findAll().stream().map(Group::getName).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.CK_OWNER, userManagementApiClient.getUsers().getData().stream().map(UserDto::getUsername).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.CK_KEY_USAGE, Arrays.stream((KeyUsage.values())).map(KeyUsage::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_LENGTH), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_STATE, Arrays.stream((KeyState.values())).map(KeyState::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_FORMAT, Arrays.stream((KeyFormat.values())).map(KeyFormat::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_TYPE, Arrays.stream((KeyType.values())).map(KeyType::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_CRYPTOGRAPHIC_ALGORITHM, Arrays.stream((KeyAlgorithm.values())).map(KeyAlgorithm::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_TOKEN_PROFILE, tokenProfileRepository.findAll().stream().map(TokenProfile::getName).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_TOKEN_INSTANCE_LABEL, tokenInstanceReferenceRepository.findAll().stream().map(TokenInstanceReference::getName).toList()) + SearchHelper.prepareSearch(FilterField.CKI_NAME), + SearchHelper.prepareSearch(FilterField.CK_GROUP, groupRepository.findAll().stream().map(Group::getName).toList()), + SearchHelper.prepareSearch(FilterField.CK_OWNER, userManagementApiClient.getUsers().getData().stream().map(UserDto::getUsername).toList()), + SearchHelper.prepareSearch(FilterField.CKI_USAGE, Arrays.stream(KeyUsage.values()).map(KeyUsage::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CKI_LENGTH), + SearchHelper.prepareSearch(FilterField.CKI_STATE, Arrays.stream(KeyState.values()).map(KeyState::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CKI_FORMAT, Arrays.stream(KeyFormat.values()).map(KeyFormat::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CKI_TYPE, Arrays.stream(KeyType.values()).map(KeyType::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CKI_CRYPTOGRAPHIC_ALGORITHM, Arrays.stream(KeyAlgorithm.values()).map(KeyAlgorithm::getCode).toList()), + SearchHelper.prepareSearch(FilterField.CK_TOKEN_PROFILE, tokenProfileRepository.findAll().stream().map(TokenProfile::getName).toList()), + SearchHelper.prepareSearch(FilterField.CK_TOKEN_INSTANCE, tokenInstanceReferenceRepository.findAll().stream().map(TokenInstanceReference::getName).toList()) + ); fields = new ArrayList<>(fields); fields.sort(new SearchFieldDataComparator()); diff --git a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java index 6ac54ec0c..c67d33fb3 100644 --- a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java @@ -33,7 +33,7 @@ import com.czertainly.core.dao.entity.workflows.TriggerAssociation; import com.czertainly.core.dao.repository.*; import com.czertainly.core.dao.repository.workflows.TriggerAssociationRepository; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.event.transaction.CertificateValidationEvent; import com.czertainly.core.event.transaction.DiscoveryFinishedEvent; import com.czertainly.core.event.transaction.DiscoveryProgressEvent; @@ -615,13 +615,14 @@ public List getSearchableFieldInformationByGroup() { final List searchFieldDataByGroupDtos = attributeEngine.getResourceSearchableFields(Resource.DISCOVERY, false); List fields = List.of( - SearchHelper.prepareSearch(SearchFieldNameEnum.CKI_NAME), - SearchHelper.prepareSearch(SearchFieldNameEnum.DISCOVERY_STATUS, Arrays.stream(DiscoveryStatus.values()).map(DiscoveryStatus::getCode).toList()), - SearchHelper.prepareSearch(SearchFieldNameEnum.DISCOVERY_START_TIME), - SearchHelper.prepareSearch(SearchFieldNameEnum.DISCOVERY_END_TIME), - SearchHelper.prepareSearch(SearchFieldNameEnum.DISCOVERY_TOTAL_CERT_DISCOVERED), - SearchHelper.prepareSearch(SearchFieldNameEnum.DISCOVERY_CONNECTOR_NAME, discoveryRepository.findDistinctConnectorName()), - SearchHelper.prepareSearch(SearchFieldNameEnum.DISCOVERY_KIND) + SearchHelper.prepareSearch(FilterField.CKI_NAME), + SearchHelper.prepareSearch(FilterField.DISCOVERY_STATUS, Arrays.stream(DiscoveryStatus.values()).map(DiscoveryStatus::getCode).toList()), + SearchHelper.prepareSearch(FilterField.DISCOVERY_START_TIME), + SearchHelper.prepareSearch(FilterField.DISCOVERY_END_TIME), + SearchHelper.prepareSearch(FilterField.DISCOVERY_TOTAL_CERT_DISCOVERED), + SearchHelper.prepareSearch(FilterField.DISCOVERY_CONNECTOR_NAME, discoveryRepository.findDistinctConnectorName()), + SearchHelper.prepareSearch(FilterField.DISCOVERY_KIND) + ); fields = new ArrayList<>(fields); diff --git a/src/main/java/com/czertainly/core/service/impl/EntityInstanceServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/EntityInstanceServiceImpl.java index b92a973e3..eee82996f 100644 --- a/src/main/java/com/czertainly/core/service/impl/EntityInstanceServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/EntityInstanceServiceImpl.java @@ -21,7 +21,7 @@ import com.czertainly.core.dao.entity.Connector; import com.czertainly.core.dao.entity.EntityInstanceReference; import com.czertainly.core.dao.repository.EntityInstanceReferenceRepository; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authz.ExternalAuthorization; import com.czertainly.core.security.authz.SecuredUUID; @@ -288,9 +288,9 @@ public List getSearchableFieldInformationByGroup() { final List searchFieldDataByGroupDtos = attributeEngine.getResourceSearchableFields(Resource.ENTITY, false); List fields = List.of( - SearchHelper.prepareSearch(SearchFieldNameEnum.ENTITY_NAME), - SearchHelper.prepareSearch(SearchFieldNameEnum.ENTITY_CONNECTOR_NAME, entityInstanceReferenceRepository.findDistinctConnectorName()), - SearchHelper.prepareSearch(SearchFieldNameEnum.ENTITY_KIND, entityInstanceReferenceRepository.findDistinctKind()) + SearchHelper.prepareSearch(FilterField.ENTITY_NAME), + SearchHelper.prepareSearch(FilterField.ENTITY_CONNECTOR_NAME, entityInstanceReferenceRepository.findDistinctConnectorName()), + SearchHelper.prepareSearch(FilterField.ENTITY_KIND, entityInstanceReferenceRepository.findDistinctKind()) ); fields = new ArrayList<>(fields); diff --git a/src/main/java/com/czertainly/core/service/impl/LocationServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/LocationServiceImpl.java index 204a61fa5..b5328b13d 100644 --- a/src/main/java/com/czertainly/core/service/impl/LocationServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/LocationServiceImpl.java @@ -25,9 +25,9 @@ import com.czertainly.api.model.core.connector.ConnectorStatus; import com.czertainly.api.model.core.enums.CertificateRequestFormat; import com.czertainly.api.model.core.location.LocationDto; +import com.czertainly.api.model.core.search.FilterFieldSource; import com.czertainly.api.model.core.search.SearchFieldDataByGroupDto; import com.czertainly.api.model.core.search.SearchFieldDataDto; -import com.czertainly.api.model.core.search.FilterFieldSource; import com.czertainly.api.model.core.v2.ClientCertificateDataResponseDto; import com.czertainly.api.model.core.v2.ClientCertificateRenewRequestDto; import com.czertainly.api.model.core.v2.ClientCertificateSignRequestDto; @@ -39,7 +39,7 @@ import com.czertainly.core.dao.repository.EntityInstanceReferenceRepository; import com.czertainly.core.dao.repository.LocationRepository; import com.czertainly.core.dao.repository.RaProfileRepository; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.event.transaction.CertificateValidationEvent; import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authz.ExternalAuthorization; @@ -55,7 +55,6 @@ import com.czertainly.core.util.FilterPredicatesBuilder; import com.czertainly.core.util.RequestValidatorHelper; import com.czertainly.core.util.SearchHelper; -import com.czertainly.core.util.converter.Sql2PredicateConverter; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; @@ -75,7 +74,6 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.*; -import java.util.function.BiFunction; @Service @Transactional @@ -1134,11 +1132,11 @@ public List getSearchableFieldInformationByGroup() { final List searchFieldDataByGroupDtos = attributeEngine.getResourceSearchableFields(Resource.LOCATION, false); List fields = List.of( - SearchHelper.prepareSearch(SearchFieldNameEnum.LOCATION_NAME), - SearchHelper.prepareSearch(SearchFieldNameEnum.LOCATION_ENTITY_INSTANCE, locationRepository.findDistinctEntityInstanceName()), - SearchHelper.prepareSearch(SearchFieldNameEnum.LOCATION_ENABLED), - SearchHelper.prepareSearch(SearchFieldNameEnum.LOCATION_SUPPORT_MULTIPLE_ENTRIES), - SearchHelper.prepareSearch(SearchFieldNameEnum.LOCATION_SUPPORT_KEY_MANAGEMENT) + SearchHelper.prepareSearch(FilterField.LOCATION_NAME), + SearchHelper.prepareSearch(FilterField.LOCATION_ENTITY_INSTANCE, locationRepository.findDistinctEntityInstanceName()), + SearchHelper.prepareSearch(FilterField.LOCATION_ENABLED), + SearchHelper.prepareSearch(FilterField.LOCATION_SUPPORT_MULTIPLE_ENTRIES), + SearchHelper.prepareSearch(FilterField.LOCATION_SUPPORT_KEY_MANAGEMENT) ); fields = new ArrayList<>(fields); diff --git a/src/main/java/com/czertainly/core/service/impl/ResourceServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/ResourceServiceImpl.java index 039464760..fa947dd9e 100644 --- a/src/main/java/com/czertainly/core/service/impl/ResourceServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/ResourceServiceImpl.java @@ -13,13 +13,13 @@ import com.czertainly.api.model.core.search.SearchFieldDataByGroupDto; import com.czertainly.api.model.core.search.SearchFieldDataDto; import com.czertainly.core.attribute.engine.AttributeEngine; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.enums.SearchFieldTypeEnum; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.*; +import com.czertainly.core.util.FilterPredicatesBuilder; import com.czertainly.core.util.SearchHelper; -import com.czertainly.core.util.converter.Sql2PredicateConverter; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.transaction.Transactional; @@ -282,24 +282,24 @@ public List listResourceRuleFilterFields(Resource res List searchFieldDataByGroupDtos = attributeEngine.getResourceSearchableFields(resource, settable); - List enums = SearchFieldNameEnum.getEnumsForResource(resource); + List enums = FilterField.getEnumsForResource(resource); List fieldDataDtos = new ArrayList<>(); - for (SearchFieldNameEnum fieldEnum : enums) { + for (FilterField fieldEnum : enums) { // If getting only settable fields, skip not settable fields if (settable && !fieldEnum.isSettable()) continue; // Filter field has a single value, don't need to provide list - if (fieldEnum.getFieldTypeEnum() != SearchFieldTypeEnum.LIST) + if (fieldEnum.getType() != SearchFieldTypeEnum.LIST) fieldDataDtos.add(SearchHelper.prepareSearch(fieldEnum)); else { // Filter field has values of an Enum - if (fieldEnum.getFieldProperty().getEnumClass() != null) - fieldDataDtos.add(SearchHelper.prepareSearch(fieldEnum, fieldEnum.getFieldProperty().getEnumClass().getEnumConstants())); + if (fieldEnum.getEnumClass() != null) + fieldDataDtos.add(SearchHelper.prepareSearch(fieldEnum, fieldEnum.getEnumClass().getEnumConstants())); // Filter field has values of all objects of another entity else if (fieldEnum.getFieldResource() != null) fieldDataDtos.add(SearchHelper.prepareSearch(fieldEnum, getObjectsForResource(fieldEnum.getFieldResource()))); // Filter field has values of all possible values of a property else { - fieldDataDtos.add(SearchHelper.prepareSearch(fieldEnum, Sql2PredicateConverter.getAllValuesOfProperty(fieldEnum.getFieldProperty().getCode(), resource, entityManager).getResultList())); + fieldDataDtos.add(SearchHelper.prepareSearch(fieldEnum, FilterPredicatesBuilder.getAllValuesOfProperty(FilterPredicatesBuilder.buildPathToProperty(fieldEnum, false), resource, entityManager).getResultList())); } } } diff --git a/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java index 5efed37e6..cad1f31ec 100644 --- a/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java @@ -6,7 +6,10 @@ import com.czertainly.api.exception.ValidationError; import com.czertainly.api.exception.ValidationException; import com.czertainly.api.model.core.auth.Resource; -import com.czertainly.api.model.core.scheduler.*; +import com.czertainly.api.model.core.scheduler.PaginationRequestDto; +import com.czertainly.api.model.core.scheduler.ScheduledJobDetailDto; +import com.czertainly.api.model.core.scheduler.ScheduledJobHistoryResponseDto; +import com.czertainly.api.model.core.scheduler.ScheduledJobsResponseDto; import com.czertainly.api.model.scheduler.SchedulerJobDto; import com.czertainly.api.model.scheduler.SchedulerRequestDto; import com.czertainly.api.model.scheduler.UpdateScheduledJob; @@ -19,22 +22,23 @@ import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.SchedulerService; +import com.czertainly.core.util.FilterPredicatesBuilder; import com.czertainly.core.util.RequestValidatorHelper; -import com.czertainly.core.util.converter.Sql2PredicateConverter; -import jakarta.persistence.criteria.*; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; import org.apache.commons.lang3.function.TriFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.function.BiFunction; import java.util.stream.Collectors; @Service @@ -103,7 +107,7 @@ public void deleteScheduledJob(final String uuid) { @Override @ExternalAuthorization(resource = Resource.SCHEDULED_JOB, action = ResourceAction.DETAIL) public ScheduledJobHistoryResponseDto getScheduledJobHistory(final SecurityFilter filter, final PaginationRequestDto paginationRequestDto, final String uuid) { - final TriFunction, CriteriaBuilder, CriteriaQuery, Predicate> additionalWhereClause = (root, cb, cr) -> Sql2PredicateConverter.constructFilterForJobHistory(cb, root, UUID.fromString(uuid)); + final TriFunction, CriteriaBuilder, CriteriaQuery, Predicate> additionalWhereClause = (root, cb, cr) -> FilterPredicatesBuilder.constructFilterForJobHistory(cb, root, UUID.fromString(uuid)); RequestValidatorHelper.revalidatePaginationRequestDto(paginationRequestDto); final Pageable pageable = PageRequest.of(paginationRequestDto.getPageNumber() - 1, paginationRequestDto.getItemsPerPage()); diff --git a/src/main/java/com/czertainly/core/service/impl/SearchServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/SearchServiceImpl.java deleted file mode 100644 index eeacf4afd..000000000 --- a/src/main/java/com/czertainly/core/service/impl/SearchServiceImpl.java +++ /dev/null @@ -1,296 +0,0 @@ -package com.czertainly.core.service.impl; - -import com.czertainly.api.exception.ValidationError; -import com.czertainly.api.exception.ValidationException; -import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; -import com.czertainly.api.model.client.certificate.SearchRequestDto; -import com.czertainly.api.model.core.search.*; -import com.czertainly.api.model.core.search.FilterConditionOperator; -import com.czertainly.core.dao.entity.Group; -import com.czertainly.core.dao.entity.RaProfile; -import com.czertainly.core.dao.repository.GroupRepository; -import com.czertainly.core.dao.repository.RaProfileRepository; -import com.czertainly.core.security.authz.SecurityFilter; -import com.czertainly.core.service.SearchService; -import jakarta.persistence.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - -@Service -@Transactional -public class SearchServiceImpl implements SearchService { - - private static final Logger logger = LoggerFactory.getLogger(SearchServiceImpl.class); - - @PersistenceContext - private EntityManager entityManager; - - @Autowired - private RaProfileRepository raProfileRepository; - - @Autowired - private GroupRepository groupRepository; - - @Override - public Object completeSearchQueryExecutor(List filters, String entity, List originalJson) { - - String sqlQuery = "select c from " + entity + " c"; - logger.debug("Executing query: {}", sqlQuery); - if (!filters.isEmpty()) { - sqlQuery = getQueryDynamicBasedOnFilter(filters, entity, originalJson, "", false, false, ""); - } - return customQueryExecutor(sqlQuery); - } - - @Override - public String getCompleteSearchQuery(List filters, String entity, String joinQuery, List originalJson, Boolean conditionOnly, Boolean nativeCode) { - - String sqlQuery = !conditionOnly ? "select c from " + entity + " c" : ""; - logger.debug("Executing query: {}", sqlQuery); - if (!filters.isEmpty()) { - sqlQuery = getQueryDynamicBasedOnFilter(filters, entity, originalJson, joinQuery, conditionOnly, nativeCode, ""); - } - return sqlQuery; - } - - - @Override - public Object customQueryExecutor(String sqlQuery) { - logger.debug("Executing query: {}", sqlQuery); - Query query = entityManager.createQuery(sqlQuery); - Object result = query.getResultList(); - return result; - } - - @Override - public DynamicSearchInternalResponse dynamicSearchQueryExecutor(SearchRequestDto searchRequestDto, String entity, List originalJson, String additionalWhereClause) { - logger.debug("Search request: {}", searchRequestDto.toString()); - Map page = getPageable(searchRequestDto); - DynamicSearchInternalResponse dynamicSearchInternalResponse = new DynamicSearchInternalResponse(); - if (searchRequestDto.getItemsPerPage() == null) { - searchRequestDto.setItemsPerPage(CertificateServiceImpl.DEFAULT_PAGE_SIZE); - } - if (searchRequestDto.getPageNumber() == null) { - searchRequestDto.setPageNumber(1); - } - String sqlQuery = getQueryDynamicBasedOnFilter(searchRequestDto.getFilters(), entity, originalJson, "", false, false, additionalWhereClause) + " GROUP BY created, uuid ORDER BY created DESC"; - Query query = entityManager.createQuery(sqlQuery); - query.setFirstResult(page.get("start")); - query.setMaxResults(searchRequestDto.getItemsPerPage()); - List result = query.getResultList(); - - if (result.isEmpty()) { - dynamicSearchInternalResponse.setTotalPages(1); - dynamicSearchInternalResponse.setTotalItems(0L); - dynamicSearchInternalResponse.setResult(new ArrayList<>()); - } else { - Query countQuery = entityManager.createQuery(sqlQuery.replace("select c from", "select COUNT(c) from").split(" GROUP BY ")[0]); - Long totalItems = (Long) countQuery.getSingleResult(); - dynamicSearchInternalResponse.setTotalPages((int) Math.ceil((double) totalItems / searchRequestDto.getItemsPerPage())); - dynamicSearchInternalResponse.setTotalItems(totalItems); - dynamicSearchInternalResponse.setResult(result); - } - if (dynamicSearchInternalResponse.getTotalPages().equals(0)) { - dynamicSearchInternalResponse.setTotalPages(1); - } - if (dynamicSearchInternalResponse.getTotalPages().equals(0)) { - dynamicSearchInternalResponse.setTotalPages(1); - } - return dynamicSearchInternalResponse; - } - - @Override - public String getQueryDynamicBasedOnFilter(List conditions, String entity, List originalJson, String joinQuery, Boolean conditionOnly, Boolean nativeCode, String additionalWhereClause) throws ValidationException { - String query; - if (joinQuery.isEmpty()) { - query = (!conditionOnly ? "select c from " + entity + " c " : "") + " WHERE " + additionalWhereClause; - } else { - query = (!conditionOnly ? "select c from " + entity + " c " : " ") + joinQuery; - if (!conditions.isEmpty()) { - query += " AND "; - } - } - List queryParts = new ArrayList<>(); - List iterableJson = new LinkedList<>(); - for (SearchFilterRequestDto requestField : conditions) { - for (SearchFieldDataDto field : originalJson) { - if (requestField.getFieldIdentifier().equals(field.getFieldIdentifier())) { - SearchFieldDataDto fieldDup = new SearchFieldDataDto(); - fieldDup.setFieldIdentifier(field.getFieldIdentifier()); - fieldDup.setType(field.getType()); - fieldDup.setFieldLabel(field.getFieldLabel()); - fieldDup.setType(field.getType()); - fieldDup.setMultiValue(field.isMultiValue()); - fieldDup.setValue(requestField.getValue()); - fieldDup.setConditions(List.of(requestField.getCondition())); - iterableJson.add(fieldDup); - } - } - } - for (SearchFieldDataDto filter : iterableJson) { - String qp = ""; - String ntvCode = ""; - if (List.of(SearchableFields.OCSP_VALIDATION, SearchableFields.CRL_VALIDATION, SearchableFields.SIGNATURE_VALIDATION).contains(filter.getField())) { - qp += " c.certificateValidationResult "; - } else { - if (nativeCode) { - ntvCode = filter.getField().getNativeCode(); - } else { - ntvCode = filter.getField().getCode(); - } - qp += " c." + ntvCode + " "; - } - if (filter.isMultiValue() && !(filter.getValue() instanceof String)) { - List whereObjects = new ArrayList<>(); - if (filter.getField().equals(SearchableFields.RA_PROFILE_NAME)) { - whereObjects.addAll(raProfileRepository.findAll().stream().filter(c -> ((List) filter.getValue()).contains(c.getName())).map(RaProfile::getUuid).map(c -> "'" + c + "'").collect(Collectors.toList())); - } else if (filter.getField().equals(SearchableFields.GROUP_NAME)) { - whereObjects.addAll(groupRepository.findAll().stream().filter(c -> ((List) filter.getValue()).contains(c.getName())).map(Group::getUuid).map(c -> "'" + c + "'").collect(Collectors.toList())); - } else { - whereObjects.addAll(((List) filter.getValue()).stream().map(i -> "'" + i.toString() + "'").collect(Collectors.toList())); - } - - if (whereObjects.isEmpty()) { - throw new ValidationException(ValidationError.create("No valid object found for search in " + filter.getFieldLabel())); - } - - if (filter.getConditions().get(0).equals(FilterConditionOperator.EQUALS)) { - qp += " IN (" + String.join(",", whereObjects) + " )"; - if(filter.getField().equals(SearchableFields.COMPLIANCE_STATUS) && ((List) filter.getValue()).contains("NA")){ - qp += " or " + ntvCode + " IS NULL "; - } - } - if (filter.getConditions().get(0).equals(FilterConditionOperator.NOT_EQUALS)) { - qp += " NOT IN (" + String.join(",", whereObjects) + " )"; - if(filter.getField().equals(SearchableFields.COMPLIANCE_STATUS) && !((List) filter.getValue()).contains("NA")){ - qp += " or " + ntvCode + " IS NOT NULL "; - } - } - - } else { - if (filter.getField().equals(SearchableFields.SIGNATURE_VALIDATION)) { - if (filter.getConditions().get(0).equals(FilterConditionOperator.SUCCESS)) { - qp += " LIKE '%\"Signature Verification\":{\"status\":\"success\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.FAILED)) { - qp += " LIKE '%\"Signature Verification\":{\"status\":\"failed\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.UNKNOWN)) { - qp += " LIKE '%\"Signature Verification\":{\"status\":\"not_checked\"%'"; - } - } else if (filter.getField().equals(SearchableFields.OCSP_VALIDATION)) { - if (filter.getConditions().get(0).equals(FilterConditionOperator.SUCCESS)) { - qp += "LIKE '%\"OCSP Verification\":{\"status\":\"success\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.FAILED)) { - qp += "LIKE '%\"OCSP Verification\":{\"status\":\"failed\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.UNKNOWN)) { - qp += "LIKE '%\"OCSP Verification\":{\"status\":\"unknown\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.EMPTY)) { - qp += "LIKE '%\"OCSP Verification\":{\"status\":\"warning\"%'"; - } - } else if (filter.getField().equals(SearchableFields.CRL_VALIDATION)) { - if (filter.getConditions().get(0).equals(FilterConditionOperator.SUCCESS)) { - qp += "LIKE '%\"CRL Verification\":{\"status\":\"success\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.FAILED)) { - qp += "LIKE '%\"CRL Verification\":{\"status\":\"failed\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.UNKNOWN)) { - qp += "LIKE '%\"CRL Verification\":{\"status\":\"unknown\"%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.EMPTY)) { - qp += "LIKE '%\"CRL Verification\":{\"status\":\"warning\"%'"; - } - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.CONTAINS) || filter.getConditions().get(0).equals(FilterConditionOperator.NOT_CONTAINS)) { - qp += filter.getConditions().get(0).getCode() + " '%" + filter.getValue().toString() + "%'"; - try{ - if(filter.getConditions().get(0).equals(FilterConditionOperator.NOT_CONTAINS)) { - qp += " or " + ntvCode + " IS NULL "; - } - }catch (Exception e){ - logger.warn("Unable to add empty query"); - } - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.STARTS_WITH)) { - qp += filter.getConditions().get(0).getCode() + " '" + filter.getValue().toString() + "%'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.ENDS_WITH)) { - qp += filter.getConditions().get(0).getCode() + " '%" + filter.getValue().toString() + "'"; - } else if (filter.getConditions().get(0).equals(FilterConditionOperator.EMPTY) || filter.getConditions().get(0).equals(FilterConditionOperator.NOT_EMPTY)) { - qp += filter.getConditions().get(0).getCode(); - } else { - if (filter.getField().equals(SearchableFields.RA_PROFILE_NAME)) { - String raProfileUuid = raProfileRepository.findByName(filter.getValue().toString()).orElseThrow(() -> new ValidationException(ValidationError.create(filter.getValue().toString() + " not found"))).getUuid().toString(); - qp += filter.getConditions().get(0).getCode() + " '" + raProfileUuid + "'"; - } else if (filter.getField().equals(SearchableFields.GROUP_NAME)) { - String groupUuid = groupRepository.findByName(filter.getValue().toString()).orElseThrow(() -> new ValidationException(ValidationError.create(filter.getValue().toString() + " not found"))).getUuid().toString(); - qp += filter.getConditions().get(0).getCode() + " '" + groupUuid + "'"; - } else { - qp += filter.getConditions().get(0).getCode() + " '" + filter.getValue().toString() + "'"; - } - } - } - if (!qp.isEmpty()) { - queryParts.add("(" + qp + ")"); - } - } - query += String.join(" AND ", queryParts); - logger.debug("Executable query: {}", query); - return query; - } - - private Map getPageable(SearchRequestDto request) throws ValidationException { - if (request.getItemsPerPage() == null) { - request.setItemsPerPage(CertificateServiceImpl.DEFAULT_PAGE_SIZE); - } - if (request.getItemsPerPage() > CertificateServiceImpl.MAX_PAGE_SIZE) { - throw new ValidationException(ValidationError.create("Maximum items per page is " + CertificateServiceImpl.MAX_PAGE_SIZE)); - } - - Integer pageStart = 0; - Integer pageEnd = request.getItemsPerPage(); - - if (request.getPageNumber() != null) { - pageStart = ((request.getPageNumber() - 1) * request.getItemsPerPage()); - pageEnd = request.getPageNumber() * request.getItemsPerPage(); - } - logger.debug("Pagination information - Start: {}, End : {}", pageStart, pageEnd); - return Map.ofEntries(Map.entry("start", pageStart), Map.entry("end", pageEnd)); - } - - @Override - public String createCriteriaBuilderString(SecurityFilter filter, Boolean addFinisher) { - String whereCondition = ""; - if (filter.getResourceFilter().areOnlySpecificObjectsAllowed()) { - String data = filter.getResourceFilter().getAllowedObjects().stream().map(UUID::toString).collect(Collectors.joining("','", "'", "'")); - whereCondition += "c.uuid" + " IN ( " + data + " )"; - } else { - if (!filter.getResourceFilter().getForbiddenObjects().isEmpty()) { - String data = filter.getResourceFilter().getForbiddenObjects().stream().map(UUID::toString).collect(Collectors.joining("','", "'", "'")); - whereCondition += "c.uuid" + " NOT IN ( " + data + " )"; - } - } - - if(filter.getParentResourceFilter() != null) { - if(filter.getParentRefProperty() == null) throw new ValidationException("Unknown parent ref property to filter by parent resource " + filter.getParentResourceFilter().getResource()); - - if (filter.getParentResourceFilter().areOnlySpecificObjectsAllowed()) { - String data = filter.getParentResourceFilter().getAllowedObjects().stream().map(UUID::toString).collect(Collectors.joining("','", "'", "'")); - whereCondition += "c." + filter.getParentRefProperty() + " IN ( " + data + " )"; - } else { - if (!filter.getParentResourceFilter().getForbiddenObjects().isEmpty()) { - String data = filter.getParentResourceFilter().getForbiddenObjects().stream().map(UUID::toString).collect(Collectors.joining("','", "'", "'")); - whereCondition += "c." + filter.getParentRefProperty() + "NOT IN ( " + data + " )"; - } - } - } - if(!whereCondition.equals("") && addFinisher){ - whereCondition = whereCondition + " AND"; - } - return whereCondition; - - } -} diff --git a/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java b/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java index 28546853d..1528e1cd7 100644 --- a/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java +++ b/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java @@ -3,6 +3,7 @@ import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; import com.czertainly.api.model.common.attribute.v2.content.AttributeContentType; import com.czertainly.api.model.common.enums.IPlatformEnum; +import com.czertainly.api.model.core.audit.AuditLogFilter; import com.czertainly.api.model.core.auth.Resource; import com.czertainly.api.model.core.cryptography.key.KeyUsage; import com.czertainly.api.model.core.search.FilterConditionOperator; @@ -11,9 +12,12 @@ import com.czertainly.core.enums.FilterField; import com.czertainly.core.enums.ResourceToClass; import com.czertainly.core.enums.SearchFieldTypeEnum; +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; import jakarta.persistence.criteria.*; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.PluralAttribute; +import org.apache.commons.lang3.StringUtils; import java.io.Serializable; import java.time.LocalDate; @@ -283,4 +287,87 @@ private static Object findEnumByCustomValue(Object valueObject, final Class enumItem = Arrays.stream(enumClass.getEnumConstants()).filter(enumValue -> enumValue.getCode().equals(valueObject.toString())).findFirst(); return enumItem.orElse(null); } + + public static Join prepareJoin(final Root root, final String joinPath) { + final StringTokenizer stz = new StringTokenizer(joinPath, "."); + Join join = root.join(stz.nextToken(), JoinType.LEFT); + while (stz.hasMoreTokens()) { + join = join.join(stz.nextToken(), JoinType.LEFT); + } + return join; + } + + public static Expression prepareExpression(final From from, final String attributeName) { + final StringTokenizer stz = new StringTokenizer(attributeName, "."); + Path path = from.get(stz.nextToken()); + while (stz.hasMoreTokens()) { + path = path.get(stz.nextToken()); + } + return path; + } + + public static CriteriaDelete prepareQueryForAuditLog(AuditLogFilter filter, CriteriaBuilder criteriaBuilder) { + CriteriaDelete criteriaQuery = criteriaBuilder.createCriteriaDelete(AuditLog.class); + final Root root = criteriaQuery.from(AuditLog.class); + List rootPredicates = new ArrayList<>(); + + if (StringUtils.isNotBlank(filter.getAuthor())) { + rootPredicates.add(criteriaBuilder.equal(root.get("author"), filter.getAuthor())); + } + + if (filter.getCreatedFrom() != null) { + rootPredicates.add(criteriaBuilder.greaterThan(root.get("created"), filter.getCreatedFrom())); + } + if (filter.getCreatedTo() != null) { + rootPredicates.add(criteriaBuilder.lessThan(root.get("created"), filter.getCreatedFrom())); + } + + if (filter.getOperation() != null) { + rootPredicates.add(criteriaBuilder.equal(root.get("operation"), filter.getOperation())); + } + + if (filter.getOperationStatus() != null) { + rootPredicates.add(criteriaBuilder.equal(root.get("operationStatus"), filter.getOperationStatus())); + } + + if (filter.getAffected() != null) { + rootPredicates.add(criteriaBuilder.equal(root.get("affected"), filter.getAffected())); + } + + if (filter.getOrigination() != null) { + rootPredicates.add(criteriaBuilder.equal(root.get("origination"), filter.getOrigination())); + } + + if (StringUtils.isNotBlank(filter.getObjectIdentifier())) { + rootPredicates.add(criteriaBuilder.equal(root.get("objectIdentifier"), filter.getObjectIdentifier())); + } + + criteriaQuery.where(criteriaBuilder.and(rootPredicates.toArray(new Predicate[]{}))); + return criteriaQuery; + } + + public static Query getAllValuesOfProperty(String property, Resource resource, EntityManager entityManager) { + Class resourceClass = ResourceToClass.getClassByResource(resource); + return entityManager.createQuery("SELECT DISTINCT " + property + " FROM " + resourceClass.getName()); + } + + public static Predicate constructFilterForJobHistory(final CriteriaBuilder cb, final Root root, final UUID scheduledJobUuid) { + final Expression expressionPath = prepareExpression(root, "scheduledJobUuid"); + return cb.equal(expressionPath, scheduledJobUuid); + } + + public static String buildPathToProperty(FilterField filterField, boolean alreadyNested) { + StringBuilder pathToPropertyBuilder = new StringBuilder(); + if (filterField.getJoinAttributes() != null) { + List joinAttributes = new ArrayList<>(filterField.getJoinAttributes().stream().map(Attribute::getName).toList()); + if (alreadyNested) joinAttributes.removeFirst(); + if (!joinAttributes.isEmpty()) { + for (String property : joinAttributes) { + pathToPropertyBuilder.append(property).append("."); + } + } + } + pathToPropertyBuilder.append(filterField.getFieldAttribute().getName()); + return pathToPropertyBuilder.toString(); + } } diff --git a/src/main/java/com/czertainly/core/util/SearchHelper.java b/src/main/java/com/czertainly/core/util/SearchHelper.java index b268d40d5..4504d83b7 100644 --- a/src/main/java/com/czertainly/core/util/SearchHelper.java +++ b/src/main/java/com/czertainly/core/util/SearchHelper.java @@ -6,7 +6,7 @@ import com.czertainly.api.model.core.search.FilterFieldType; import com.czertainly.api.model.core.search.SearchFieldDataDto; import com.czertainly.core.comparator.SearchFieldDataComparator; -import com.czertainly.core.enums.SearchFieldNameEnum; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.enums.SearchFieldTypeEnum; import com.czertainly.core.model.SearchFieldObject; @@ -17,20 +17,20 @@ public class SearchHelper { private static final String SEARCH_LABEL_TEMPLATE = "%s (%s)"; - public static SearchFieldDataDto prepareSearch(final SearchFieldNameEnum fieldNameEnum) { + public static SearchFieldDataDto prepareSearch(final FilterField fieldNameEnum) { return prepareSearch(fieldNameEnum, null); } - public static SearchFieldDataDto prepareSearch(final SearchFieldNameEnum fieldNameEnum, final Object values) { + public static SearchFieldDataDto prepareSearch(final FilterField fieldNameEnum, final Object values) { final SearchFieldDataDto fieldDataDto = new SearchFieldDataDto(); - fieldDataDto.setFieldIdentifier(fieldNameEnum.getFieldProperty().name()); - fieldDataDto.setFieldLabel(fieldNameEnum.getFieldLabel()); - fieldDataDto.setMultiValue(fieldNameEnum.getFieldTypeEnum().isMultiValue()); - fieldDataDto.setConditions(fieldNameEnum.getFieldTypeEnum().getFieldType() == FilterFieldType.BOOLEAN && fieldNameEnum.getFieldProperty().getExpectedValue() != null ? List.of(FilterConditionOperator.EQUALS, FilterConditionOperator.NOT_EQUALS) : fieldNameEnum.getFieldTypeEnum().getConditions()); - fieldDataDto.setType(fieldNameEnum.getFieldTypeEnum().getFieldType()); + fieldDataDto.setFieldIdentifier(fieldNameEnum.name()); + fieldDataDto.setFieldLabel(fieldNameEnum.getLabel()); + fieldDataDto.setMultiValue(fieldNameEnum.getType().isMultiValue()); + fieldDataDto.setConditions(fieldNameEnum.getType().getFieldType() == FilterFieldType.BOOLEAN && fieldNameEnum.getExpectedValue() != null ? List.of(FilterConditionOperator.EQUALS, FilterConditionOperator.NOT_EQUALS) : fieldNameEnum.getType().getConditions()); + fieldDataDto.setType(fieldNameEnum.getType().getFieldType()); fieldDataDto.setValue(values); - if (fieldNameEnum.getFieldProperty().getEnumClass() != null) { - fieldDataDto.setPlatformEnum(PlatformEnum.findByClass(fieldNameEnum.getFieldProperty().getEnumClass())); + if (fieldNameEnum.getEnumClass() != null) { + fieldDataDto.setPlatformEnum(PlatformEnum.findByClass(fieldNameEnum.getEnumClass())); } return fieldDataDto; diff --git a/src/main/java/com/czertainly/core/util/converter/Sql2PredicateConverter.java b/src/main/java/com/czertainly/core/util/converter/Sql2PredicateConverter.java deleted file mode 100644 index 5fb334494..000000000 --- a/src/main/java/com/czertainly/core/util/converter/Sql2PredicateConverter.java +++ /dev/null @@ -1,464 +0,0 @@ -package com.czertainly.core.util.converter; - -import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; -import com.czertainly.api.model.common.attribute.v2.content.AttributeContentType; -import com.czertainly.api.model.common.enums.IPlatformEnum; -import com.czertainly.api.model.core.audit.AuditLogFilter; -import com.czertainly.api.model.core.auth.Resource; -import com.czertainly.api.model.core.cryptography.key.KeyUsage; -import com.czertainly.api.model.core.search.FilterConditionOperator; -import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.FilterFieldType; -import com.czertainly.api.model.core.search.SearchableFields; -import com.czertainly.core.dao.entity.*; -import com.czertainly.core.enums.ResourceToClass; -import com.czertainly.core.enums.SearchFieldNameEnum; -import com.czertainly.core.enums.SearchFieldTypeEnum; -import com.czertainly.core.model.SearchFieldObject; -import jakarta.persistence.EntityManager; -import jakarta.persistence.Query; -import jakarta.persistence.criteria.*; -import org.apache.commons.lang3.StringUtils; -import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; - -import java.util.*; - -public class Sql2PredicateConverter { - - private Sql2PredicateConverter() { - } - - private static final String OCSP_VERIFICATION = "%\"OCSP Verification\":{\"status\":\"%STATUS%\"%"; - private static final String SIGNATURE_VERIFICATION = "%\"Signature Verification\":{\"status\":\"%STATUS%\"%"; - private static final String CRL_VERIFICATION = "%\"CRL Verification\":{\"status\":\"%STATUS%\"%"; - - public static Predicate mapSearchFilter2Predicates(final List dtos, final CriteriaBuilder criteriaBuilder, final Root root, final List objectUUIDsToBeFiltered) { - final List predicates = new ArrayList<>(); - boolean hasFilteredAttributes = false; - for (final SearchFilterRequestDto dto : dtos) { - if (dto.getFieldSource() == FilterFieldSource.PROPERTY) { - predicates.add(mapSearchFilter2Predicate(dto, criteriaBuilder, root)); - } else { - hasFilteredAttributes = true; - } - } - final Predicate propertyPredicates = criteriaBuilder.and(predicates.toArray(new Predicate[]{})); - if (objectUUIDsToBeFiltered != null && !dtos.isEmpty() && hasFilteredAttributes) { - Predicate uuidOrPredicate = root.get("uuid").in(objectUUIDsToBeFiltered); - if (root.getJavaType().equals(CryptographicKeyItem.class)) { - uuidOrPredicate = criteriaBuilder.or( - uuidOrPredicate, - prepareExpression(root, "cryptographicKey.uuid").in(objectUUIDsToBeFiltered)); - } - return criteriaBuilder.and(propertyPredicates, uuidOrPredicate); - } - return propertyPredicates; - } - - public static Predicate mapSearchFilter2Predicates(final List dtos, final CriteriaBuilder criteriaBuilder, final Root root) { - return mapSearchFilter2Predicates(dtos, criteriaBuilder, root, null); - } - - public static Predicate mapSearchFilter2Predicate(final SearchFilterRequestDto dto, final CriteriaBuilder criteriaBuilder, final Root root) { - return preparePredicateByConditions(dto, criteriaBuilder, root); - } - - private static Predicate preparePredicateByConditions(final SearchFilterRequestDto dto, final CriteriaBuilder criteriaBuilder, final Root root) { - final List predicates = new ArrayList<>(); - final List objects = readAndCheckIncomingValues(dto); - for (final Object valueObject : objects) { - predicates.add(processPredicate(criteriaBuilder, root, dto, valueObject)); - } - return predicates.size() > 1 ? criteriaBuilder.or(predicates.toArray(new Predicate[]{})) : predicates.get(0); - } - - private static Predicate processPredicate(final CriteriaBuilder criteriaBuilder, final Root root, final SearchFilterRequestDto dto, final Object valueObject) { - - final SearchableFields searchableFields = SearchableFields.valueOf(dto.getFieldIdentifier()); - final FilterConditionOperator filterConditionOperator = checkOrReplaceSearchCondition(dto, searchableFields); - final SearchFieldTypeEnum searchFieldTypeEnum = SearchFieldNameEnum.getEnumBySearchableFields(searchableFields).getFieldTypeEnum(); - final boolean isDateFormat = SearchFieldTypeEnum.DATE.equals(searchFieldTypeEnum) || SearchFieldTypeEnum.DATETIME.equals(searchFieldTypeEnum); - final Predicate predicate = checkCertificateValidationResult(root, criteriaBuilder, dto, valueObject, searchableFields); - if (predicate == null) { - final Object expressionValue = prepareValue(valueObject, searchableFields); - final SearchFieldObject searchFieldObject = SearchFieldTypeEnum.DATETIME.equals(searchFieldTypeEnum) ? new SearchFieldObject(AttributeContentType.DATETIME) : null; - return buildPredicateByCondition(criteriaBuilder, filterConditionOperator, null, root, searchableFields, expressionValue, isDateFormat, FilterFieldType.BOOLEAN.equals(searchFieldTypeEnum.getFieldType()), dto, searchFieldObject); - } - return predicate; - } - - private static Predicate buildPredicateByCondition(final CriteriaBuilder criteriaBuilder, FilterConditionOperator filterConditionOperator, Expression expression, Root root, SearchableFields searchableFields, Object expressionValue, final boolean isDateFormat, final boolean isBoolean, final SearchFilterRequestDto dto, SearchFieldObject searchFieldObject) { - if (expression == null) { - if (searchableFields.getPathToBeJoin() == null) { - expression = prepareExpression(root, searchableFields.getCode()); - } else { - final Join join = prepareJoin(root, searchableFields.getPathToBeJoin()); - expression = join.get(searchableFields.getCode()); - } - } - - if (expressionValue == null && dto.getValue() != null) { - expressionValue = dto.getValue().toString(); - } - - if (isDateFormat) { - if (searchFieldObject == null) { - searchFieldObject = new SearchFieldObject(AttributeContentType.DATE); - } - return prepareDateTimePredicate(criteriaBuilder, filterConditionOperator, expression, expressionValue != null ? expressionValue.toString() : null, searchFieldObject); - } - - Predicate predicate = null; - if (isBoolean) { - if (searchableFields == null || searchableFields.getExpectedValue() == null) { - switch (filterConditionOperator) { - case EQUALS -> - predicate = criteriaBuilder.equal(expression.as(Boolean.class), Boolean.parseBoolean(expressionValue != null ? expressionValue.toString() : null)); - case NOT_EQUALS -> - predicate = criteriaBuilder.notEqual(expression.as(Boolean.class), Boolean.parseBoolean(expressionValue != null ? expressionValue.toString() : null)); - } - return predicate; - } else { - final Boolean booleanValue = Boolean.parseBoolean(expressionValue != null ? expressionValue.toString() : null); - expressionValue = searchableFields.getExpectedValue(); - if (FilterConditionOperator.EQUALS.equals(filterConditionOperator) && !booleanValue) { - filterConditionOperator = FilterConditionOperator.NOT_EQUALS; - } else if (FilterConditionOperator.NOT_EQUALS.equals(filterConditionOperator) && !booleanValue) { - filterConditionOperator = FilterConditionOperator.EQUALS; - } - } - } - - switch (filterConditionOperator) { - case EQUALS -> predicate = criteriaBuilder.equal(expression, expressionValue); - case NOT_EQUALS -> { - if (searchableFields != null && searchableFields.getPathToBeJoin() != null) { - predicate = criteriaBuilder.or(criteriaBuilder.and(criteriaBuilder.notEqual(expression, expressionValue), criteriaBuilder.equal(expression, expressionValue)), criteriaBuilder.isNull(expression)); - } else { - predicate = criteriaBuilder.or(criteriaBuilder.notEqual(expression, expressionValue), criteriaBuilder.isNull(expression)); - } - } - case STARTS_WITH -> predicate = criteriaBuilder.like(expression, expressionValue + "%"); - case ENDS_WITH -> predicate = criteriaBuilder.like(expression, "%" + expressionValue); - case CONTAINS -> predicate = criteriaBuilder.like(expression, "%" + expressionValue + "%"); - case NOT_CONTAINS -> predicate = criteriaBuilder.or( - criteriaBuilder.notLike(expression, "%" + expressionValue + "%"), - retrievePredicateForNull(criteriaBuilder, root, searchableFields, expression) - ); - case EMPTY -> predicate = retrievePredicateForNull(criteriaBuilder, root, searchableFields, expression); - case NOT_EMPTY -> predicate = criteriaBuilder.isNotNull(expression); - case GREATER, LESSER -> { - if (filterConditionOperator.equals(FilterConditionOperator.GREATER)) { - predicate = criteriaBuilder.greaterThan(expression.as(Integer.class), Integer.valueOf(dto.getValue().toString())); - } else { - predicate = criteriaBuilder.lessThan(expression.as(Integer.class), Integer.valueOf(dto.getValue().toString())); - } - } - } - return predicate; - } - - private static Predicate retrievePredicateForNull(final CriteriaBuilder criteriaBuilder, final Root root, final SearchableFields searchableFields, final Expression expression) { - if (searchableFields != null && searchableFields.getCode().contains(".")) { - int indexOfDot = searchableFields.getCode().lastIndexOf("."); - final String mainPropertyString = searchableFields.getCode().substring(0, indexOfDot); - final Expression mainExpression = prepareExpression(root, mainPropertyString); - - return mainExpression instanceof SqmPluralValuedSimplePath ? criteriaBuilder.equal(criteriaBuilder.size(mainExpression), criteriaBuilder.literal(0)) : criteriaBuilder.isNull(mainExpression); - } else { - return criteriaBuilder.isNull(expression); - } - } - - private static Predicate prepareDateTimePredicate(final CriteriaBuilder criteriaBuilder, final FilterConditionOperator filterConditionOperator, final Expression expression, final String value, final SearchFieldObject searchFieldObject) { - Predicate dateTimePredicate = null; - switch (filterConditionOperator) { - case EQUALS -> { - switch (searchFieldObject.getAttributeContentType()) { - case DATETIME -> - dateTimePredicate = criteriaBuilder.equal(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateTimeFormat(value)); - case DATE -> - dateTimePredicate = criteriaBuilder.equal(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateFormat(value)); - case TIME -> - dateTimePredicate = criteriaBuilder.equal(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalTimeFormat(value)); - } - } - case NOT_EQUALS -> { - switch (searchFieldObject.getAttributeContentType()) { - case DATETIME -> - dateTimePredicate = criteriaBuilder.notEqual(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateTimeFormat(value)); - case DATE -> - dateTimePredicate = criteriaBuilder.notEqual(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateFormat(value)); - case TIME -> - dateTimePredicate = criteriaBuilder.notEqual(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalTimeFormat(value)); - } - } - case GREATER -> { - switch (searchFieldObject.getAttributeContentType()) { - case DATETIME -> - dateTimePredicate = criteriaBuilder.greaterThan(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateTimeFormat(value)); - case DATE -> - dateTimePredicate = criteriaBuilder.greaterThan(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateFormat(value)); - case TIME -> - dateTimePredicate = criteriaBuilder.greaterThan(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalTimeFormat(value)); - } - } - case LESSER -> { - switch (searchFieldObject.getAttributeContentType()) { - case DATETIME -> - dateTimePredicate = criteriaBuilder.lessThan(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateTimeFormat(value)); - case DATE -> - dateTimePredicate = criteriaBuilder.lessThan(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalDateFormat(value)); - case TIME -> - dateTimePredicate = criteriaBuilder.lessThan(expression.as(searchFieldObject.getDateTimeFormatClass()), searchFieldObject.getLocalTimeFormat(value)); - } - } - } - return dateTimePredicate; - } - - private static FilterConditionOperator checkOrReplaceSearchCondition(final SearchFilterRequestDto dto, final SearchableFields searchableFields) { - if (searchableFields.getEnumClass() != null - && searchableFields.getEnumClass().equals(KeyUsage.class)) { - if (dto.getCondition().equals(FilterConditionOperator.EQUALS)) { - return FilterConditionOperator.CONTAINS; - } else if (dto.getCondition().equals(FilterConditionOperator.NOT_EQUALS)) { - return FilterConditionOperator.NOT_CONTAINS; - } - } - return dto.getCondition(); - } - - public static Expression prepareExpression(final From from, final String attributeName) { - final StringTokenizer stz = new StringTokenizer(attributeName, "."); - Path path = from.get(stz.nextToken()); - while (stz.hasMoreTokens()) { - path = path.get(stz.nextToken()); - } - return path; - } - - public static Join prepareJoin(final Root root, final String joinPath) { - final StringTokenizer stz = new StringTokenizer(joinPath, "."); - Join join = root.join(stz.nextToken(), JoinType.LEFT); - while (stz.hasMoreTokens()) { - join = join.join(stz.nextToken(), JoinType.LEFT); - } - return join; - } - - private static Object prepareValue(final Object valueObject, final SearchableFields searchableFields) { - if (searchableFields.getEnumClass() != null) { - if (searchableFields.getEnumClass().equals(KeyUsage.class)) { - final KeyUsage keyUsage = (KeyUsage) findEnumByCustomValue(valueObject, searchableFields); - if (keyUsage != null) { - return keyUsage.getBitmask(); - } - } - return findEnumByCustomValue(valueObject, searchableFields); - } - return valueObject == null ? null : valueObject.toString(); - } - - private static Object findEnumByCustomValue(Object valueObject, final SearchableFields searchableFields) { - Optional enumItem = Arrays.stream(searchableFields.getEnumClass().getEnumConstants()).filter(enumValue -> enumValue.getCode().equals(valueObject.toString())).findFirst(); - return enumItem.isPresent() ? enumItem.get() : null; - } - - private static List readAndCheckIncomingValues(final SearchFilterRequestDto dto) { - final List objects = new ArrayList<>(); - if (dto.getValue() instanceof List) { - objects.addAll((List) dto.getValue()); - } else { - objects.add(dto.getValue()); - } - return objects; - } - - private static Predicate checkCertificateValidationResult(final Root root, final CriteriaBuilder criteriaBuilder, final SearchFilterRequestDto dto, final Object valueObject, final SearchableFields searchableFields) { - if (List.of(SearchableFields.OCSP_VALIDATION, SearchableFields.CRL_VALIDATION, SearchableFields.SIGNATURE_VALIDATION).contains(searchableFields)) { - String textToBeFormatted = null; - switch (searchableFields) { - case OCSP_VALIDATION -> textToBeFormatted = OCSP_VERIFICATION; - case SIGNATURE_VALIDATION -> textToBeFormatted = SIGNATURE_VERIFICATION; - case CRL_VALIDATION -> textToBeFormatted = CRL_VERIFICATION; - } - if (textToBeFormatted != null) { - switch (dto.getCondition()) { - case EQUALS -> { - return criteriaBuilder.like(root.get("certificateValidationResult"), formatCertificateVerificationResultByStatus(textToBeFormatted, valueObject.toString())); - } - case NOT_EQUALS -> { - return criteriaBuilder.notLike(root.get("certificateValidationResult"), formatCertificateVerificationResultByStatus(textToBeFormatted, valueObject.toString())); - } - } - } - } - return null; - } - - private static String formatCertificateVerificationResultByStatus(final String textToBeFormatted, final String statusCode) { - return textToBeFormatted.replace("%STATUS%", statusCode); - } - - public static CriteriaDelete prepareQueryForAuditLog(AuditLogFilter filter, CriteriaBuilder criteriaBuilder) { - CriteriaDelete criteriaQuery = criteriaBuilder.createCriteriaDelete(AuditLog.class); - final Root root = criteriaQuery.from(AuditLog.class); - List rootPredicates = new ArrayList<>(); - - if (StringUtils.isNotBlank(filter.getAuthor())) { - rootPredicates.add(criteriaBuilder.equal(root.get("author"), filter.getAuthor())); - } - - if (filter.getCreatedFrom() != null) { - rootPredicates.add(criteriaBuilder.greaterThan(root.get("created"), filter.getCreatedFrom())); - } - if (filter.getCreatedTo() != null) { - rootPredicates.add(criteriaBuilder.lessThan(root.get("created"), filter.getCreatedFrom())); - - } - - if (filter.getOperation() != null) { - rootPredicates.add(criteriaBuilder.equal(root.get("operation"), filter.getOperation())); - } - - if (filter.getOperationStatus() != null) { - rootPredicates.add(criteriaBuilder.equal(root.get("operationStatus"), filter.getOperationStatus())); - } - - if (filter.getAffected() != null) { - rootPredicates.add(criteriaBuilder.equal(root.get("affected"), filter.getAffected())); - } - - if (filter.getOrigination() != null) { - rootPredicates.add(criteriaBuilder.equal(root.get("origination"), filter.getOrigination())); - } - - if (StringUtils.isNotBlank(filter.getObjectIdentifier())) { - rootPredicates.add(criteriaBuilder.equal(root.get("objectIdentifier"), filter.getObjectIdentifier())); - } - - criteriaQuery.where(criteriaBuilder.and(rootPredicates.toArray(new Predicate[]{}))); - return criteriaQuery; - - } - - public static CriteriaQueryDataObject prepareQueryToSearchIntoAttributes(final List searchableFields, final List dtos, final CriteriaBuilder criteriaBuilder, final Resource resource) { - final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(UUID.class); - final Root root = criteriaQuery.from(AttributeContent2Object.class); - - criteriaQuery.distinct(true).select(root.get("objectUuid")); - - final List rootPredicates = new ArrayList<>(); - - for (final SearchFilterRequestDto dto : dtos) { - final FilterFieldSource filterFieldSource = dto.getFieldSource(); - if (filterFieldSource == FilterFieldSource.CUSTOM || filterFieldSource == FilterFieldSource.META || filterFieldSource == FilterFieldSource.DATA) { - - // --- SUB QUERY --- - final Subquery subquery = criteriaQuery.subquery(UUID.class); - final Root subRoot = subquery.from(AttributeContent2Object.class); - final Join joinAttributeContentItem = subRoot.join("attributeContentItem"); - - subquery.select(subRoot.get("objectUuid")); - - final List subPredicates = new ArrayList<>(); - subPredicates.add(criteriaBuilder.equal(subRoot.get("objectType"), resource)); - - final String identifier = dto.getFieldIdentifier(); - final String[] fieldIdentifier = identifier.split("\\|"); - final AttributeContentType fieldAttributeContentType = AttributeContentType.valueOf(fieldIdentifier[1]); - final String fieldIdentifierName = fieldIdentifier[0]; - final Optional searchFieldObject = - searchableFields.stream().filter(attr -> - attr.getAttributeType().equals(filterFieldSource.getAttributeType()) - && attr.getAttributeName().equals(fieldIdentifierName) - && attr.getAttributeContentType().equals(fieldAttributeContentType)).findFirst(); - - if (searchFieldObject.isPresent()) { - - final SearchFieldObject searchField = searchFieldObject.get(); - - final Subquery jsonValueQuery = subquery.subquery(String.class); - final Root subACIRoot = jsonValueQuery.from(AttributeContentItem.class); - - final Expression expressionFunctionToGetJsonValue = criteriaBuilder.function("jsonb_extract_path_text", String.class, subACIRoot.get("json"), - criteriaBuilder.literal(searchField.getAttributeContentType().isFilterByData() ? "data" : "reference")); - - final Predicate predicateForContentType = criteriaBuilder.equal(prepareExpression(subACIRoot, "attributeDefinition.contentType"), searchField.getAttributeContentType()); - final Predicate predicateToKeepRelationWithUpperQuery = criteriaBuilder.equal(subACIRoot.get("uuid"), joinAttributeContentItem.get("uuid")); - final Predicate predicateGroup = criteriaBuilder.equal(prepareExpression(subACIRoot, "attributeDefinition.type"), searchField.getAttributeType()); - final Predicate predicateAttributeName = criteriaBuilder.equal(prepareExpression(subACIRoot, "attributeDefinition.name"), fieldIdentifierName); - - jsonValueQuery.select(expressionFunctionToGetJsonValue); - jsonValueQuery.where(predicateForContentType, predicateToKeepRelationWithUpperQuery, predicateAttributeName, predicateGroup); - - final List expressionPredicates = new ArrayList<>(); - final List expressionValues = readAndCheckIncomingValues(dto); - for (final Object expressionValue : expressionValues) { - final Predicate expressionPredicate = buildPredicateByCondition(criteriaBuilder, dto.getCondition(), jsonValueQuery, null, null, expressionValue, searchField.isDateTimeFormat(), searchField.isBooleanFormat(), dto, searchField); - expressionPredicates.add(expressionPredicate); - } - subPredicates.add(expressionPredicates.size() > 1 ? criteriaBuilder.or(expressionPredicates.toArray(new Predicate[]{})) : expressionPredicates.get(0)); - - subquery.where(subPredicates.toArray(new Predicate[]{})); - rootPredicates.add(criteriaBuilder.in(root.get("objectUuid")).value(subquery)); - } - } - } - - final CriteriaQueryDataObject cqdo = new CriteriaQueryDataObject(); - cqdo.setRoot(root); - cqdo.setCriteriaQuery(criteriaQuery); - cqdo.setPredicate(criteriaBuilder.and(rootPredicates.toArray(new Predicate[]{}))); - return cqdo; - } - - public static Predicate constructFilterForJobHistory(final CriteriaBuilder cb, final Root root, final UUID scheduledJobUuid) { - final Expression expressionPath = prepareExpression(root, "scheduledJobUuid"); - return cb.equal(expressionPath, scheduledJobUuid); - } - - public static Query getAllValuesOfProperty(String property, Resource resource, EntityManager entityManager) { - Class resourceClass = ResourceToClass.getClassByResource(resource); - return entityManager.createQuery("SELECT DISTINCT " + property + " FROM " + resourceClass.getName()); - } - - - public static class CriteriaQueryDataObject { - - private CriteriaQuery criteriaQuery; - - private Root root; - - private Predicate predicate; - - public CriteriaQuery getCriteriaQuery() { - return criteriaQuery; - } - - public void setCriteriaQuery(CriteriaQuery criteriaQuery) { - this.criteriaQuery = criteriaQuery; - } - - public Predicate getPredicate() { - return predicate; - } - - public void setPredicate(Predicate predicate) { - this.predicate = predicate; - } - - public Root getRoot() { - return root; - } - - public void setRoot(Root root) { - this.root = root; - } - } - - -} - - diff --git a/src/test/java/com/czertainly/core/evaluator/RuleEvaluatorTest.java b/src/test/java/com/czertainly/core/evaluator/RuleEvaluatorTest.java index 7b9d8cc9b..8c3fb66c9 100644 --- a/src/test/java/com/czertainly/core/evaluator/RuleEvaluatorTest.java +++ b/src/test/java/com/czertainly/core/evaluator/RuleEvaluatorTest.java @@ -18,12 +18,12 @@ import com.czertainly.api.model.core.workflows.ExecutionType; import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; import com.czertainly.core.dao.entity.*; import com.czertainly.core.dao.entity.workflows.*; import com.czertainly.core.dao.repository.*; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.service.AttributeService; import com.czertainly.core.service.ResourceObjectAssociationService; import com.czertainly.core.util.BaseSpringBootTest; @@ -83,6 +83,12 @@ static void authServiceProperties(DynamicPropertyRegistry registry) { @Autowired private ResourceObjectAssociationService associationService; + @Autowired + private LocationRepository locationRepository; + + @Autowired + private CertificateLocationRepository certificateLocationRepository; + private Certificate certificate; private ConditionItem condition; @@ -122,7 +128,7 @@ public void testCertificateEvaluatorOnProperties() throws RuleException { certificate.setCommonName("Common Name"); condition.setFieldSource(FilterFieldSource.PROPERTY); - condition.setFieldIdentifier(SearchableFields.COMMON_NAME.toString()); + condition.setFieldIdentifier(FilterField.COMMON_NAME.toString()); condition.setOperator(FilterConditionOperator.NOT_EMPTY); Assertions.assertTrue(certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); condition.setValue("Common Name"); @@ -155,15 +161,30 @@ public void testCertificateEvaluatorOnProperties() throws RuleException { certificate = certificateRepository.save(certificate); condition.setOperator(FilterConditionOperator.EQUALS); - condition.setFieldIdentifier(SearchableFields.GROUP_NAME.toString()); + condition.setFieldIdentifier(FilterField.GROUP_NAME.toString()); condition.setValue(group.getName()); Assertions.assertTrue(certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); certificate.setTrustedCa(true); - condition.setFieldIdentifier(SearchableFields.TRUSTED_CA.toString()); + condition.setFieldIdentifier(FilterField.TRUSTED_CA.toString()); condition.setOperator(FilterConditionOperator.EQUALS); condition.setValue(true); Assertions.assertTrue(certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); + + Location location = new Location(); + location.setName("loc"); + locationRepository.save(location); + CertificateLocation certificateLocation = new CertificateLocation(); + certificateLocation.setLocation(location); + certificateLocation.setCertificate(certificate); + certificateLocationRepository.save(certificateLocation); + certificate.setLocations(new HashSet<>(List.of(certificateLocation))); + condition.setFieldIdentifier(FilterField.CERT_LOCATION_NAME.name()); + condition.setOperator(FilterConditionOperator.EQUALS); + condition.setValue("loc"); + Assertions.assertTrue(certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); + + } @Test @@ -177,7 +198,7 @@ public void testExceptions() throws RuleException, ParseException { condition.setFieldSource(FilterFieldSource.PROPERTY); Assertions.assertThrows(RuleException.class, () -> certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); - condition.setFieldIdentifier(SearchableFields.COMMON_NAME.toString()); + condition.setFieldIdentifier(FilterField.COMMON_NAME.toString()); condition.setFieldSource(FilterFieldSource.PROPERTY); condition.setOperator(FilterConditionOperator.GREATER); Assertions.assertThrows(RuleException.class, () -> certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); @@ -198,14 +219,14 @@ public void testExceptions() throws RuleException, ParseException { public void testEvaluatorDate() throws RuleException, ParseException { certificate.setNotBefore(new SimpleDateFormat(("yyyy-MM-dd HH:mm:ss")).parse("2019-12-01 22:10:15")); condition.setFieldSource(FilterFieldSource.PROPERTY); - condition.setFieldIdentifier(SearchableFields.NOT_BEFORE.toString()); + condition.setFieldIdentifier(FilterField.NOT_BEFORE.toString()); condition.setValue("2010-12-12"); condition.setOperator(FilterConditionOperator.GREATER); Assertions.assertTrue(certificateRuleEvaluator.evaluateConditionItem(condition, certificate, Resource.CERTIFICATE)); DiscoveryHistory discovery = new DiscoveryHistory(); discovery.setStartTime(new SimpleDateFormat(("yyyy-MM-dd HH:mm:ss")).parse("2019-12-01 22:10:15")); - condition.setFieldIdentifier(SearchableFields.DISCOVERY_START_TIME.toString()); + condition.setFieldIdentifier(FilterField.DISCOVERY_START_TIME.toString()); condition.setValue("2019-12-01T22:10:00.274+00:00"); Assertions.assertTrue(discoveryHistoryRuleEvaluator.evaluateConditionItem(condition, discovery, Resource.DISCOVERY)); @@ -218,11 +239,11 @@ public void testsCryptographicKeyRuleEvaluator() throws RuleException { cryptographicKey.setName("Key"); ConditionItem condition = new ConditionItem(); condition.setFieldSource(FilterFieldSource.PROPERTY); - condition.setFieldIdentifier(SearchableFields.CKI_NAME.toString()); + condition.setFieldIdentifier(FilterField.CKI_NAME.toString()); condition.setOperator(FilterConditionOperator.NOT_EMPTY); Assertions.assertTrue(cryptographicKeyRuleEvaluator.evaluateConditionItem(condition, cryptographicKey, Resource.CRYPTOGRAPHIC_KEY)); cryptographicKey.setLength(256); - condition.setFieldIdentifier(SearchableFields.CKI_LENGTH.toString()); + condition.setFieldIdentifier(FilterField.CKI_LENGTH.toString()); condition.setOperator(FilterConditionOperator.GREATER); condition.setValue(255); Assertions.assertTrue(cryptographicKeyRuleEvaluator.evaluateConditionItem(condition, cryptographicKey, Resource.CRYPTOGRAPHIC_KEY)); @@ -286,7 +307,7 @@ public void testCertificateRuleEvaluatorMeta() throws RuleException, AttributeEx @Test public void testSetCertificateGroup() throws RuleException { executionItem.setFieldSource(FilterFieldSource.PROPERTY); - executionItem.setFieldIdentifier(SearchableFields.GROUP_NAME.toString()); + executionItem.setFieldIdentifier(FilterField.GROUP_NAME.toString()); Group group = new Group(); group.setName("groupName"); group = groupRepository.save(group); @@ -306,7 +327,7 @@ public void testSetCertificateGroup() throws RuleException { @Test public void testSetCertificateOwner() throws RuleException { executionItem.setFieldSource(FilterFieldSource.PROPERTY); - executionItem.setFieldIdentifier(SearchableFields.OWNER.toString()); + executionItem.setFieldIdentifier(FilterField.OWNER.toString()); executionItem.setData(UUID.randomUUID()); mockServer = new WireMockServer(10001); @@ -356,7 +377,7 @@ public void testSetRaProfile() throws RuleException { certificate.setCertificateContent(certificateContent); executionItem.setFieldSource(FilterFieldSource.PROPERTY); - executionItem.setFieldIdentifier(SearchableFields.RA_PROFILE_NAME.toString()); + executionItem.setFieldIdentifier(FilterField.RA_PROFILE_NAME.toString()); executionItem.setData(raProfile.getUuid()); certificateRuleEvaluator.performActions(trigger, certificate, new TriggerHistory()); Assertions.assertEquals(raProfile.getName(), certificate.getRaProfile().getName()); diff --git a/src/test/java/com/czertainly/core/search/DiscoveryHistorySearchTest.java b/src/test/java/com/czertainly/core/search/DiscoveryHistorySearchTest.java index c09228d5c..41b03af8a 100644 --- a/src/test/java/com/czertainly/core/search/DiscoveryHistorySearchTest.java +++ b/src/test/java/com/czertainly/core/search/DiscoveryHistorySearchTest.java @@ -20,11 +20,11 @@ import com.czertainly.api.model.core.discovery.DiscoveryStatus; import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; import com.czertainly.core.dao.entity.*; import com.czertainly.core.dao.repository.*; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.DiscoveryService; import com.czertainly.core.util.BaseSpringBootTest; @@ -209,7 +209,7 @@ public void testInsertedAttributes() { @Test public void testFilterDataByNameContains() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_NAME.name(), FilterConditionOperator.CONTAINS, "test_discovery")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_NAME.name(), FilterConditionOperator.CONTAINS, "test_discovery")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(4, responseDto.getDiscoveries().size()); } @@ -217,7 +217,7 @@ public void testFilterDataByNameContains() { @Test public void testFilterDataByNameEquals() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_NAME.name(), FilterConditionOperator.EQUALS, "test_discovery2")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_NAME.name(), FilterConditionOperator.EQUALS, "test_discovery2")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(1, responseDto.getDiscoveries().size()); } @@ -225,7 +225,7 @@ public void testFilterDataByNameEquals() { @Test public void testFilterDataByStartTime() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_START_TIME.name(), FilterConditionOperator.GREATER, "2020-05-06T10:10:10.000Z")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_START_TIME.name(), FilterConditionOperator.GREATER, "2020-05-06T10:10:10.000Z")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(2, responseDto.getDiscoveries().size()); } @@ -233,7 +233,7 @@ public void testFilterDataByStartTime() { @Test public void testFilterDataByEndTime() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_END_TIME.name(), FilterConditionOperator.LESSER, "2020-02-02T10:10:10.000Z")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_END_TIME.name(), FilterConditionOperator.LESSER, "2020-02-02T10:10:10.000Z")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(1, responseDto.getDiscoveries().size()); } @@ -241,7 +241,7 @@ public void testFilterDataByEndTime() { @Test public void testFilterDataByStatus() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_STATUS.name(), FilterConditionOperator.NOT_EQUALS, DiscoveryStatus.COMPLETED.getCode())); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_STATUS.name(), FilterConditionOperator.NOT_EQUALS, DiscoveryStatus.COMPLETED.getCode())); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(2, responseDto.getDiscoveries().size()); } @@ -249,7 +249,7 @@ public void testFilterDataByStatus() { @Test public void testFilterDataByTotalCertificateDiscovered() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_TOTAL_CERT_DISCOVERED.name(), FilterConditionOperator.GREATER, 10)); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_TOTAL_CERT_DISCOVERED.name(), FilterConditionOperator.GREATER, 10)); final SearchRequestDto searchRequestDto = new SearchRequestDto(); searchRequestDto.setFilters(filters); final DiscoveryResponseDto responseDto = discoveryService.listDiscoveries(SecurityFilter.create(), searchRequestDto); @@ -259,7 +259,7 @@ public void testFilterDataByTotalCertificateDiscovered() { @Test public void testFilterDataByKind() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_KIND.name(), FilterConditionOperator.NOT_CONTAINS, "TEST3")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_KIND.name(), FilterConditionOperator.NOT_CONTAINS, "TEST3")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(2, responseDto.getDiscoveries().size()); } @@ -267,7 +267,7 @@ public void testFilterDataByKind() { @Test public void testFilterDataByConnectorName() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_CONNECTOR_NAME.name(), FilterConditionOperator.EQUALS, "connector1")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_CONNECTOR_NAME.name(), FilterConditionOperator.EQUALS, "connector1")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(3, responseDto.getDiscoveries().size()); } @@ -275,8 +275,8 @@ public void testFilterDataByConnectorName() { @Test public void testFilterDataByConnectorNameAndStatus() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_CONNECTOR_NAME.name(), FilterConditionOperator.EQUALS, "connector1")); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_STATUS.name(), FilterConditionOperator.EQUALS, DiscoveryStatus.COMPLETED.getCode())); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_CONNECTOR_NAME.name(), FilterConditionOperator.EQUALS, "connector1")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_STATUS.name(), FilterConditionOperator.EQUALS, DiscoveryStatus.COMPLETED.getCode())); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(1, responseDto.getDiscoveries().size()); } @@ -284,9 +284,9 @@ public void testFilterDataByConnectorNameAndStatus() { @Test public void testFilterDataByConnectorNameAndKind() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_CONNECTOR_NAME.name(), FilterConditionOperator.EQUALS, "connector1")); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_KIND.name(), FilterConditionOperator.STARTS_WITH, "kindTEST")); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.DISCOVERY_START_TIME.name(), FilterConditionOperator.LESSER, "2020-02-01T10:10:10.000Z")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_CONNECTOR_NAME.name(), FilterConditionOperator.EQUALS, "connector1")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_KIND.name(), FilterConditionOperator.STARTS_WITH, "kindTEST")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.DISCOVERY_START_TIME.name(), FilterConditionOperator.LESSER, "2020-02-01T10:10:10.000Z")); final DiscoveryResponseDto responseDto = retrieveTheDiscoveriesBySearch(filters); Assertions.assertEquals(1, responseDto.getDiscoveries().size()); } diff --git a/src/test/java/com/czertainly/core/search/EntityInstanceSearchTest.java b/src/test/java/com/czertainly/core/search/EntityInstanceSearchTest.java index f5728f6a6..ec468de8f 100644 --- a/src/test/java/com/czertainly/core/search/EntityInstanceSearchTest.java +++ b/src/test/java/com/czertainly/core/search/EntityInstanceSearchTest.java @@ -18,7 +18,6 @@ import com.czertainly.api.model.core.connector.ConnectorStatus; import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; import com.czertainly.core.dao.entity.Connector; @@ -27,6 +26,7 @@ import com.czertainly.core.dao.repository.ConnectorRepository; import com.czertainly.core.dao.repository.EntityInstanceReferenceRepository; import com.czertainly.core.dao.repository.LocationRepository; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.EntityInstanceService; import com.czertainly.core.util.BaseSpringBootTest; @@ -144,7 +144,7 @@ public void testInsertedData() { @Test public void testEntityByName() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.ENTITY_NAME.name(), FilterConditionOperator.EQUALS, "entity-ref-2")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.ENTITY_NAME.name(), FilterConditionOperator.EQUALS, "entity-ref-2")); final EntityInstanceResponseDto responseDto = retrieveTheEntitiesBySearch(filters); Assertions.assertEquals(1, responseDto.getEntities().size()); } @@ -152,7 +152,7 @@ public void testEntityByName() { @Test public void testEntityByConnectorName() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.ENTITY_CONNECTOR_NAME.name(), FilterConditionOperator.CONTAINS, "Connector")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.ENTITY_CONNECTOR_NAME.name(), FilterConditionOperator.CONTAINS, "Connector")); final EntityInstanceResponseDto responseDto = retrieveTheEntitiesBySearch(filters); Assertions.assertEquals(3, responseDto.getEntities().size()); } @@ -160,7 +160,7 @@ public void testEntityByConnectorName() { @Test public void testEntityByKind() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.ENTITY_KIND.name(), FilterConditionOperator.CONTAINS, "test-kind")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.ENTITY_KIND.name(), FilterConditionOperator.CONTAINS, "test-kind")); final EntityInstanceResponseDto responseDto = retrieveTheEntitiesBySearch(filters); Assertions.assertEquals(2, responseDto.getEntities().size()); } diff --git a/src/test/java/com/czertainly/core/search/LocationsSearchTest.java b/src/test/java/com/czertainly/core/search/LocationsSearchTest.java index 730a8fdb9..3ae2db2f6 100644 --- a/src/test/java/com/czertainly/core/search/LocationsSearchTest.java +++ b/src/test/java/com/czertainly/core/search/LocationsSearchTest.java @@ -18,7 +18,6 @@ import com.czertainly.api.model.core.connector.ConnectorStatus; import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; import com.czertainly.core.dao.entity.Connector; @@ -27,6 +26,7 @@ import com.czertainly.core.dao.repository.ConnectorRepository; import com.czertainly.core.dao.repository.EntityInstanceReferenceRepository; import com.czertainly.core.dao.repository.LocationRepository; +import com.czertainly.core.enums.FilterField; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.LocationService; import com.czertainly.core.util.BaseSpringBootTest; @@ -146,7 +146,7 @@ public void testInsertedData() { @Test public void testLocationByName() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.LOCATION_NAME.name(), FilterConditionOperator.EQUALS, "location1")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.LOCATION_NAME.name(), FilterConditionOperator.EQUALS, "location1")); final LocationsResponseDto responseDto = retrieveLocationsBySearch(filters); Assertions.assertEquals(2, responseDto.getLocations().size()); } @@ -154,7 +154,7 @@ public void testLocationByName() { @Test public void testLocationByInstanceName() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.LOCATION_ENTITY_INSTANCE.name(), FilterConditionOperator.ENDS_WITH, "instance-name-3")); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.LOCATION_ENTITY_INSTANCE.name(), FilterConditionOperator.ENDS_WITH, "instance-name-3")); final LocationsResponseDto responseDto = retrieveLocationsBySearch(filters); Assertions.assertEquals(1, responseDto.getLocations().size()); } @@ -162,7 +162,7 @@ public void testLocationByInstanceName() { @Test public void testLocationByEnabled() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.LOCATION_ENABLED.name(), FilterConditionOperator.EQUALS, true)); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.LOCATION_ENABLED.name(), FilterConditionOperator.EQUALS, true)); final LocationsResponseDto responseDto = retrieveLocationsBySearch(filters); Assertions.assertEquals(1, responseDto.getLocations().size()); } @@ -170,7 +170,7 @@ public void testLocationByEnabled() { @Test public void testLocationBySupportMultipleEntries() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.LOCATION_SUPPORT_MULTIPLE_ENTRIES.name(), FilterConditionOperator.NOT_EQUALS, true)); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.LOCATION_SUPPORT_MULTIPLE_ENTRIES.name(), FilterConditionOperator.NOT_EQUALS, true)); final LocationsResponseDto responseDto = retrieveLocationsBySearch(filters); Assertions.assertEquals(1, responseDto.getLocations().size()); } @@ -178,7 +178,7 @@ public void testLocationBySupportMultipleEntries() { @Test public void testLocationBySupportKeyManagement() { final List filters = new ArrayList<>(); - filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, SearchableFields.LOCATION_SUPPORT_KEY_MANAGEMENT.name(), FilterConditionOperator.EQUALS, false)); + filters.add(new SearchFilterRequestDtoDummy(FilterFieldSource.PROPERTY, FilterField.LOCATION_SUPPORT_KEY_MANAGEMENT.name(), FilterConditionOperator.EQUALS, false)); final LocationsResponseDto responseDto = retrieveLocationsBySearch(filters); Assertions.assertEquals(1, responseDto.getLocations().size()); } diff --git a/src/test/java/com/czertainly/core/service/CertificateServiceTest.java b/src/test/java/com/czertainly/core/service/CertificateServiceTest.java index 36b806972..214c510b2 100644 --- a/src/test/java/com/czertainly/core/service/CertificateServiceTest.java +++ b/src/test/java/com/czertainly/core/service/CertificateServiceTest.java @@ -26,6 +26,7 @@ import com.czertainly.core.util.MetaDefinitions; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; +import jakarta.transaction.NotSupportedException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -384,7 +385,7 @@ public void testSearchableFields() { @Test @Disabled("Necessary to resolve handling non transactional method inside transactional test. Objects stored in setUp method are not available since transaction will be suspended in bulk delete.") - public void testBulkRemove() throws NotFoundException { + public void testBulkRemove() throws NotFoundException, NotSupportedException { RemoveCertificateDto request = new RemoveCertificateDto(); request.setUuids(List.of(certificate.getUuid().toString())); diff --git a/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java b/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java index f99378442..2954dabd7 100644 --- a/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java +++ b/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java @@ -1,11 +1,34 @@ package com.czertainly.core.util; +import com.czertainly.api.exception.AlreadyExistException; +import com.czertainly.api.exception.AttributeException; +import com.czertainly.api.exception.NotFoundException; +import com.czertainly.api.model.client.attribute.custom.CustomAttributeCreateRequestDto; +import com.czertainly.api.model.client.attribute.custom.CustomAttributeDefinitionDetailDto; +import com.czertainly.api.model.client.certificate.CertificateResponseDto; import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; -import com.czertainly.api.model.common.attribute.v2.content.AttributeContentType; +import com.czertainly.api.model.client.certificate.SearchRequestDto; +import com.czertainly.api.model.common.attribute.v2.content.*; +import com.czertainly.api.model.common.enums.cryptography.KeyType; +import com.czertainly.api.model.core.auth.Resource; +import com.czertainly.api.model.core.certificate.CertificateState; import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; +import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.dao.entity.Certificate; +import com.czertainly.core.dao.entity.CryptographicKey; +import com.czertainly.core.dao.entity.CryptographicKeyItem; +import com.czertainly.core.dao.entity.Group; +import com.czertainly.core.dao.repository.CertificateRepository; +import com.czertainly.core.dao.repository.CryptographicKeyItemRepository; +import com.czertainly.core.dao.repository.CryptographicKeyRepository; +import com.czertainly.core.dao.repository.GroupRepository; +import com.czertainly.core.enums.FilterField; +import com.czertainly.core.security.authz.SecurityFilter; +import com.czertainly.core.service.AttributeService; +import com.czertainly.core.service.CertificateService; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; @@ -15,25 +38,61 @@ import org.hibernate.query.sqm.tree.predicate.*; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; /** * Tests for class {@link FilterPredicatesBuilder} */ @SpringBootTest -public class FilterPredicatesBuilderTest extends BaseSpringBootTest { +class FilterPredicatesBuilderTest extends BaseSpringBootTest { @Autowired private EntityManager entityManager; + @Autowired + private AttributeService attributeService; + + @Autowired + private AttributeEngine attributeEngine; + + @Autowired + private CertificateService certificateService; + + @Autowired + private CertificateRepository certificateRepository; + + @Autowired + private GroupRepository groupRepository; + + @Autowired + private CryptographicKeyRepository cryptographicKeyRepository; + + @Autowired + private CryptographicKeyItemRepository cryptographicKeyItemRepository; + private CriteriaBuilder criteriaBuilder; + private Certificate certificate1; + private Certificate certificate2; + private Certificate certificate3; + private CriteriaQuery criteriaQuery; private Root root; @@ -41,15 +100,56 @@ public class FilterPredicatesBuilderTest extends BaseSpringBootTest { private final String TEST_VALUE = "test"; private final String TEST_DATE_VALUE = "2022-01-01"; + @DynamicPropertySource + static void authServiceProperties(DynamicPropertyRegistry registry) { + registry.add("auth-service.base-url", () -> "http://localhost:10002"); + } + + @BeforeEach - public void prepare() { + public void prepare() throws AlreadyExistException, AttributeException, NotFoundException { + + certificate1 = new Certificate(); + certificate2 = new Certificate(); + certificate3 = new Certificate(); + certificateRepository.saveAll(List.of(certificate1, certificate2, certificate3)); + + CustomAttributeDefinitionDetailDto intAttribute = createCustomAttribute("integer", AttributeContentType.INTEGER); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, intAttribute.getName(), List.of(new IntegerAttributeContent("ref", 1))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, intAttribute.getName(), List.of(new IntegerAttributeContent("ref", 2))); + + CustomAttributeDefinitionDetailDto decimalAttribute = createCustomAttribute("decimal", AttributeContentType.FLOAT); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, decimalAttribute.getName(), List.of(new FloatAttributeContent("ref", 1.3f))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, decimalAttribute.getName(), List.of(new FloatAttributeContent("ref", 1.33f))); + + + CustomAttributeDefinitionDetailDto booleanAttribute = createCustomAttribute("boolean", AttributeContentType.BOOLEAN); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, booleanAttribute.getName(), List.of(new BooleanAttributeContent("ref", true))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, booleanAttribute.getName(), List.of(new BooleanAttributeContent("ref", false))); + + CustomAttributeDefinitionDetailDto attribute = createCustomAttribute("string", AttributeContentType.STRING); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, attribute.getName(), List.of(new StringAttributeContent("ref", "value1"))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, attribute.getName(), List.of(new StringAttributeContent("ref", "value2"))); + + CustomAttributeDefinitionDetailDto dateAttribute = createCustomAttribute("date", AttributeContentType.DATE); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, dateAttribute.getName(), List.of(new DateAttributeContent(LocalDate.parse("2025-05-16")))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, dateAttribute.getName(), List.of(new DateAttributeContent(LocalDate.parse("2025-05-20")))); + + CustomAttributeDefinitionDetailDto dateTimeAttribute = createCustomAttribute("datetime", AttributeContentType.DATETIME); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, dateTimeAttribute.getName(), List.of(new DateTimeAttributeContent(ZonedDateTime.parse("2018-12-26T20:28:33.213+05:30")))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, dateTimeAttribute.getName(), List.of(new DateTimeAttributeContent(ZonedDateTime.parse("2018-12-28T20:28:33.213+05:30")))); + + CustomAttributeDefinitionDetailDto timeAttribute = createCustomAttribute("time", AttributeContentType.TIME); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, timeAttribute.getName(), List.of(new TimeAttributeContent(LocalTime.parse("10:15:45")))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, timeAttribute.getName(), List.of(new TimeAttributeContent(LocalTime.parse("11:15:45")))); + criteriaBuilder = entityManager.getCriteriaBuilder(); criteriaQuery = criteriaBuilder.createQuery(Certificate.class); root = criteriaQuery.from(Certificate.class); } @Test - public void testEqualsPredicate() { + void testEqualsPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.EQUALS))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertInstanceOf(SqmComparisonPredicate.class, predicateTest); @@ -58,7 +158,7 @@ public void testEqualsPredicate() { } @Test - public void testNotEqualsPredicate() { + void testNotEqualsPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.NOT_EQUALS))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertInstanceOf(SqmJunctionPredicate.class, predicateTest); @@ -76,14 +176,14 @@ public void testNotEqualsPredicate() { } @Test - public void testContainsPredicate() { + void testContainsPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.CONTAINS))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); testLikePredicate(predicateTest, "%" + TEST_VALUE + "%"); } @Test - public void testNotContainsPredicate() { + void testNotContainsPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.NOT_CONTAINS))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertInstanceOf(SqmJunctionPredicate.class, predicateTest); @@ -101,21 +201,21 @@ public void testNotContainsPredicate() { } @Test - public void testStartWithPredicate() { + void testStartWithPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.STARTS_WITH))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); testLikePredicate(predicateTest, TEST_VALUE + "%"); } @Test - public void testEndWithPredicate() { + void testEndWithPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.ENDS_WITH))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); testLikePredicate(predicateTest, "%" + TEST_VALUE); } @Test - public void testEmptyPredicate() { + void testEmptyPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.EMPTY))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertInstanceOf(SqmNullnessPredicate.class, predicateTest); @@ -123,7 +223,7 @@ public void testEmptyPredicate() { } @Test - public void testNotEmptyPredicate() { + void testNotEmptyPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.NOT_EMPTY))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertInstanceOf(SqmNullnessPredicate.class, predicateTest); @@ -131,7 +231,7 @@ public void testNotEmptyPredicate() { } @Test - public void testGreaterPredicate() { + void testGreaterPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.GREATER))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertEquals(ComparisonOperator.GREATER_THAN, ((SqmComparisonPredicate) predicateTest).getSqmOperator()); @@ -139,7 +239,7 @@ public void testGreaterPredicate() { } @Test - public void testLesserPredicate() { + void testLesserPredicate() { final Predicate filterPredicate = FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, List.of(prepareDummyFilterRequest(FilterConditionOperator.LESSER))); Predicate predicateTest = ((SqmJunctionPredicate) filterPredicate).getPredicates().getFirst(); Assertions.assertEquals(ComparisonOperator.LESS_THAN, ((SqmComparisonPredicate) predicateTest).getSqmOperator()); @@ -147,66 +247,578 @@ public void testLesserPredicate() { } @Test - public void testCombinedFilters() { + void testCombinedFilters() { List testFilters = new ArrayList<>(); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.SUBJECTDN, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.META, SearchableFields.CKI_LENGTH, AttributeContentType.STRING, FilterConditionOperator.EQUALS, 1)); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.CUSTOM, SearchableFields.SERIAL_NUMBER, AttributeContentType.INTEGER, FilterConditionOperator.NOT_EQUALS, "123")); + testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, FilterField.SUBJECTDN, FilterConditionOperator.EQUALS, "test")); + testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); + testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.META, FilterField.CKI_LENGTH, AttributeContentType.STRING, FilterConditionOperator.EQUALS, 1)); + testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.CUSTOM, FilterField.SERIAL_NUMBER, AttributeContentType.INTEGER, FilterConditionOperator.NOT_EQUALS, "123")); final SqmJunctionPredicate filterPredicate = (SqmJunctionPredicate) FilterPredicatesBuilder.getFiltersPredicate(criteriaBuilder, criteriaQuery, root, testFilters); - Assertions.assertEquals(4,(filterPredicate.getPredicates().size())); + Assertions.assertEquals(4, (filterPredicate.getPredicates().size())); Assertions.assertInstanceOf(SqmExistsPredicate.class, filterPredicate.getPredicates().get(2)); Assertions.assertInstanceOf(SqmExistsPredicate.class, filterPredicate.getPredicates().get(3)); Assertions.assertTrue(filterPredicate.getPredicates().get(3).isNegated()); } + @Test + @Disabled("In process of being fixed") + void testIntegerAttribute() { + + final String ATTR_IDENTIFIER = "integer|INTEGER"; + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, 1))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, 1))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, 1))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, 1))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, 2))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, 2))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + @Test + @Disabled("In process of being fixed, greater/lesser or equal not working") + void testDecimalAttribute() { + + final String ATTR_IDENTIFIER = "decimal|FLOAT"; + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, 1.3f))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, 1.3f))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, 1.3f))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, 1.3f))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, 1.33f))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, 1.33f))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + @Test + @Disabled("In process of being fixed") + void testBooleanAttribute() throws AlreadyExistException, AttributeException, NotFoundException { + final String ATTR_IDENTIFIER = "boolean|BOOLEAN"; + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, true))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, true))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + } + + @Test + void testStringAttribute() { + + final String ATTR_IDENTIFIER = "string|STRING"; + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, "value1"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.CONTAINS, "value"))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_CONTAINS, "1"))); + Assertions.assertEquals(Set.of(certificate3.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, "value1"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.STARTS_WITH, "v"))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.ENDS_WITH, "1"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + @Test + @Disabled("In process of being fixed") + void testDateAttribute() { + + final String ATTR_IDENTIFIER = "date|DATE"; + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, "2025-05-20"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, "2025-05-20"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + @Test + @Disabled("In process of being fixed") + void testDateTimeAttribute() { + + final String ATTR_IDENTIFIER = "datetime|DATETIME"; + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, "2018-12-26T20:28:33.213+05:30"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, "2018-12-26T20:28:33.213+05:30"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, "2018-12-26T20:28:33.213+05:30"))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, "2018-12-26T20:28:33.213+05:30"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, "2018-12-28T20:28:33.213+05:30"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, "2018-12-28T20:28:33.213+05:30"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + @Test + @Disabled("In process of being fixed") + void testTimeAttribute() { + + final String ATTR_IDENTIFIER = "time|TIME"; + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, "10:15:45"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, "10:15:45"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, "10:15:45"))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, "10:15:45"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, "11:15:45"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, "11:15:45"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + + @Test + @Disabled("Foreign key not working properly in test environment") + void testFiltersOnGroups() throws NotFoundException { + Group group1 = new Group(); + group1.setName("group 1"); + Group group2 = new Group(); + group2.setName("group 2"); + groupRepository.saveAll(List.of(group1, group2)); + + certificateService.updateCertificateGroups(certificate1.getSecuredUuid(), Set.of(group1.getUuid())); + certificateService.updateCertificateGroups(certificate2.getSecuredUuid(), Set.of(group1.getUuid(), group2.getUuid())); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.GROUP_NAME.name(), FilterConditionOperator.EQUALS, (Serializable) List.of("group 1")))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + } + + @Test + void testFiltersOnOwners() throws NotFoundException { + WireMockServer mockServer = new WireMockServer(10002); + mockServer.start(); + WireMock.configureFor("localhost", mockServer.port()); + + mockServer.stubFor(WireMock.get(WireMock.urlPathMatching("/auth/users/[^/]+")).willReturn( + WireMock.okJson("{ \"username\": \"owner1\"}") + )); + + certificateService.updateOwner(certificate1.getSecuredUuid(), String.valueOf(UUID.randomUUID())); + + mockServer.stubFor(WireMock.get(WireMock.urlPathMatching("/auth/users/[^/]+")).willReturn( + WireMock.okJson("{ \"username\": \"owner2\"}") + )); + + certificateService.updateOwner(certificate2.getSecuredUuid(), String.valueOf(UUID.randomUUID())); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.OWNER.name(), FilterConditionOperator.EQUALS, (Serializable) List.of("owner1")))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.OWNER.name(), FilterConditionOperator.EQUALS, (Serializable) List.of("owner1", "owner2")))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.OWNER.name(), FilterConditionOperator.NOT_EQUALS, (Serializable) List.of("owner1")))); + Assertions.assertEquals(Set.of(certificate3.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.OWNER.name(), FilterConditionOperator.NOT_EQUALS, (Serializable) List.of("owner1", "owner2")))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.OWNER.name(), FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.OWNER.name(), FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + } + + @Test + void testStringProperty() { + certificate1.setCommonName("name1"); + certificate2.setCommonName("name2"); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.EQUALS, "name1"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.CONTAINS, "name"))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.NOT_CONTAINS, "1"))); + Assertions.assertEquals(Set.of(certificate3.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.NOT_EQUALS, "name1"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.STARTS_WITH, "n"))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME.name(), FilterConditionOperator.ENDS_WITH, "2"))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + + } + + @Test + void testEnumProperty() { + certificate1.setState(CertificateState.FAILED); + certificate2.setState(CertificateState.REVOKED); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.CERTIFICATE_STATE.name(), FilterConditionOperator.EQUALS, (Serializable) List.of(CertificateState.FAILED.getCode())))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.CERTIFICATE_STATE.name(), FilterConditionOperator.EQUALS, (Serializable) List.of(CertificateState.FAILED.getCode(), CertificateState.REVOKED.getCode())))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.CERTIFICATE_STATE.name(), FilterConditionOperator.NOT_EQUALS, (Serializable) List.of(CertificateState.FAILED.getCode())))); + Assertions.assertEquals(Set.of(certificate3.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.CERTIFICATE_STATE.name(), FilterConditionOperator.NOT_EQUALS, (Serializable) List.of(CertificateState.FAILED.getCode(), CertificateState.REVOKED.getCode())))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.CERTIFICATE_STATE.name(), FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.CERTIFICATE_STATE.name(), FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + } + + @Test + void testDateProperty() throws ParseException { + certificate1.setNotAfter(new SimpleDateFormat(("yyyy-MM-dd HH:mm:ss")).parse("2025-05-16 22:10:15")); + certificate2.setNotAfter(new SimpleDateFormat(("yyyy-MM-dd HH:mm:ss")).parse("2025-05-20 22:10:15")); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.EQUALS, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.NOT_EQUALS, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.GREATER, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.GREATER_OR_EQUAL, "2025-05-16"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.LESSER, "2025-05-20"))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.LESSER_OR_EQUAL, "2025-05-20"))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto7 = new SearchRequestDto(); + searchRequestDto7.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto7))); + + SearchRequestDto searchRequestDto8 = new SearchRequestDto(); + searchRequestDto8.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.NOT_AFTER.name(), FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto8))); + } + + @Test + @Disabled("Foreign key not working properly in test environment") + void testHasPrivateKey() { + CryptographicKey cryptographicKey = new CryptographicKey(); + cryptographicKey = cryptographicKeyRepository.save(cryptographicKey); + CryptographicKeyItem cryptographicKeyItem = new CryptographicKeyItem(); + cryptographicKeyItem.setType(KeyType.PRIVATE_KEY); + cryptographicKeyItem.setCryptographicKey(cryptographicKey); + cryptographicKeyItemRepository.save(cryptographicKeyItem); + certificate1.setKey(cryptographicKey); + + System.out.println("Key UUID: " + cryptographicKey.getUuid()); + System.out.println("Certificate Key UUID: " + certificate1.getKey().getUuid()); + + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.PRIVATE_KEY.name(), FilterConditionOperator.EQUALS, true))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + } + + @Test + void testListProperty() { + certificate1.setKeySize(1); + certificate2.setKeySize(2); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.KEY_SIZE.name(), FilterConditionOperator.EQUALS, (Serializable) List.of(1)))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.KEY_SIZE.name(), FilterConditionOperator.EQUALS, (Serializable) List.of(1, 2)))); + Assertions.assertEquals(Set.of(certificate1.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.KEY_SIZE.name(), FilterConditionOperator.NOT_EQUALS, (Serializable) List.of(1)))); + Assertions.assertEquals(Set.of(certificate3.getUuid(), certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto6 = new SearchRequestDto(); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.KEY_SIZE.name(), FilterConditionOperator.NOT_EQUALS, (Serializable) List.of(1, 2)))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.KEY_SIZE.name(), FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + SearchRequestDto searchRequestDto5 = new SearchRequestDto(); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.KEY_SIZE.name(), FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); + + } + + @Test + void testBooleanProperty() { + certificate1.setTrustedCa(true); + certificate2.setTrustedCa(false); + + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.TRUSTED_CA.name(), FilterConditionOperator.EQUALS, true))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + SearchRequestDto searchRequestDto2 = new SearchRequestDto(); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.TRUSTED_CA.name(), FilterConditionOperator.NOT_EQUALS, true))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); + + SearchRequestDto searchRequestDto3 = new SearchRequestDto(); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.TRUSTED_CA.name(), FilterConditionOperator.EMPTY, null))); + Assertions.assertEquals(Set.of(certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); + + SearchRequestDto searchRequestDto4 = new SearchRequestDto(); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.TRUSTED_CA.name(), FilterConditionOperator.NOT_EMPTY, null))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); + + } + + + private Set getUuidsFromListCertificatesResponse(CertificateResponseDto certificateResponseDto) { + return certificateResponseDto.getCertificates().stream().map(c -> UUID.fromString(c.getUuid())).collect(Collectors.toSet()); + } + private void testLikePredicate(final Predicate predicate, final String value) { Assertions.assertInstanceOf(SqmLikePredicate.class, predicate); Assertions.assertEquals(value, ((SqmLikePredicate) predicate).getPattern().toHqlString()); } + private SearchFilterRequestDTODummy prepareDummyFilterRequest(final FilterConditionOperator condition) { SearchFilterRequestDTODummy dummy = null; switch (condition) { case EQUALS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.EQUALS, TEST_VALUE); case NOT_EQUALS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.NOT_EQUALS, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.NOT_EQUALS, TEST_VALUE); case CONTAINS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.CONTAINS, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.CONTAINS, TEST_VALUE); case NOT_CONTAINS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.NOT_CONTAINS, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.NOT_CONTAINS, TEST_VALUE); case STARTS_WITH -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.STARTS_WITH, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.STARTS_WITH, TEST_VALUE); case ENDS_WITH -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.ENDS_WITH, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.ENDS_WITH, TEST_VALUE); case EMPTY -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.EMPTY, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.EMPTY, TEST_VALUE); case NOT_EMPTY -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.NOT_EMPTY, TEST_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.COMMON_NAME, FilterConditionOperator.NOT_EMPTY, TEST_VALUE); case GREATER -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.NOT_AFTER, FilterConditionOperator.GREATER, TEST_DATE_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.NOT_AFTER, FilterConditionOperator.GREATER, TEST_DATE_VALUE); case LESSER -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.NOT_BEFORE, FilterConditionOperator.LESSER, TEST_DATE_VALUE); + dummy = new SearchFilterRequestDTODummy(FilterField.NOT_BEFORE, FilterConditionOperator.LESSER, TEST_DATE_VALUE); } return dummy; } + private CustomAttributeDefinitionDetailDto createCustomAttribute(String name, AttributeContentType contentType) throws AlreadyExistException, AttributeException { + CustomAttributeCreateRequestDto customAttributeRequest = new CustomAttributeCreateRequestDto(); + customAttributeRequest.setName(name); + customAttributeRequest.setLabel(name); + customAttributeRequest.setResources(List.of(Resource.CERTIFICATE)); + customAttributeRequest.setContentType(contentType); + + return attributeService.createCustomAttribute(customAttributeRequest); + } + } class SearchFilterRequestDTODummy extends SearchFilterRequestDto { - private SearchableFields fieldTest; + private FilterField fieldTest; private FilterConditionOperator conditionTest; private Serializable valueTest; private FilterFieldSource filterFieldSource; private String fieldIdentifier; - public SearchFilterRequestDTODummy(SearchableFields fieldTest, FilterConditionOperator conditionTest, Serializable valueTest) { + public SearchFilterRequestDTODummy(FilterField fieldTest, FilterConditionOperator conditionTest, Serializable valueTest) { this.fieldTest = fieldTest; this.conditionTest = conditionTest; this.valueTest = valueTest; @@ -214,7 +826,7 @@ public SearchFilterRequestDTODummy(SearchableFields fieldTest, FilterConditionOp this.filterFieldSource = FilterFieldSource.PROPERTY; } - public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, SearchableFields fieldTest, FilterConditionOperator conditionTest, Serializable valueTest) { + public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, FilterField fieldTest, FilterConditionOperator conditionTest, Serializable valueTest) { this.filterFieldSource = filterFieldSource; this.fieldTest = fieldTest; this.conditionTest = conditionTest; @@ -222,7 +834,7 @@ public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, Searchab this.fieldIdentifier = fieldTest.name(); } - public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, SearchableFields fieldTest, AttributeContentType attributeContentType, FilterConditionOperator conditionTest, Serializable valueTest) { + public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, FilterField fieldTest, AttributeContentType attributeContentType, FilterConditionOperator conditionTest, Serializable valueTest) { this.filterFieldSource = filterFieldSource; this.fieldTest = fieldTest; this.conditionTest = conditionTest; @@ -230,7 +842,7 @@ public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, Searchab this.fieldIdentifier = fieldTest.name() + "|" + attributeContentType.name(); } - public SearchableFields getField() { + public FilterField getField() { return fieldTest; } @@ -242,7 +854,7 @@ public Serializable getValue() { return valueTest; } - public void setFieldTest(SearchableFields fieldTest) { + public void setFieldTest(FilterField fieldTest) { this.fieldTest = fieldTest; this.fieldIdentifier = fieldTest.name(); } diff --git a/src/test/java/com/czertainly/core/util/converter/Sql2PredicateConverterTest.java b/src/test/java/com/czertainly/core/util/converter/Sql2PredicateConverterTest.java deleted file mode 100644 index 1e02ac4d1..000000000 --- a/src/test/java/com/czertainly/core/util/converter/Sql2PredicateConverterTest.java +++ /dev/null @@ -1,383 +0,0 @@ -package com.czertainly.core.util.converter; - -import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; -import com.czertainly.api.model.common.attribute.v2.AttributeType; -import com.czertainly.api.model.common.attribute.v2.content.AttributeContentType; -import com.czertainly.api.model.core.auth.Resource; -import com.czertainly.api.model.core.certificate.CertificateValidationStatus; -import com.czertainly.api.model.core.search.FilterConditionOperator; -import com.czertainly.api.model.core.search.FilterFieldSource; -import com.czertainly.api.model.core.search.SearchableFields; -import com.czertainly.core.dao.entity.Certificate; -import com.czertainly.core.dao.entity.CryptographicKeyItem; -import com.czertainly.core.model.SearchFieldObject; -import com.czertainly.core.util.BaseSpringBootTest; -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; -import org.hibernate.query.sqm.ComparisonOperator; -import org.hibernate.query.sqm.tree.predicate.*; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Tests for class {@link Sql2PredicateConverter} - */ -@SpringBootTest -public class Sql2PredicateConverterTest extends BaseSpringBootTest { - - @Autowired - private EntityManager entityManager; - - private CriteriaBuilder criteriaBuilder; - - private CriteriaQuery criteriaQuery; - - private Root root; - - private Root rootCryptoKeyItem; - - private final String TEST_VALUE = "test"; - private final String TEST_DATE_VALUE = "2022-01-01"; - - private final String TEST_VERIFICATION_TEXT = "{\"status\":\"%STATUS%\""; - - @BeforeEach - public void prepare() { - criteriaBuilder = entityManager.getCriteriaBuilder(); - criteriaQuery = criteriaBuilder.createQuery(Certificate.class); - root = criteriaQuery.from(Certificate.class); - } - - @Test - public void testEqualsPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.EQUALS), criteriaBuilder, root); - Assertions.assertInstanceOf(SqmComparisonPredicate.class, predicateTest); - Assertions.assertEquals(ComparisonOperator.EQUAL, ((SqmComparisonPredicate) predicateTest).getSqmOperator()); - Assertions.assertEquals(TEST_VALUE, ((SqmComparisonPredicate) predicateTest).getRightHandExpression().toHqlString()); - } - - @Test - public void testNotEqualsPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.NOT_EQUALS), criteriaBuilder, root); - Assertions.assertInstanceOf(SqmJunctionPredicate.class, predicateTest); - - final SqmJunctionPredicate sqmJunctionPredicate = ((SqmJunctionPredicate) predicateTest); - for (final SqmPredicate predicate : sqmJunctionPredicate.getPredicates()) { - Assertions.assertTrue(predicate instanceof SqmComparisonPredicate || predicate instanceof SqmNullnessPredicate); - if (predicate instanceof SqmComparisonPredicate) { - Assertions.assertEquals(ComparisonOperator.NOT_EQUAL, ((SqmComparisonPredicate) predicate).getSqmOperator()); - Assertions.assertEquals(TEST_VALUE, ((SqmComparisonPredicate) predicate).getRightHandExpression().toHqlString()); - } else if (predicate instanceof SqmNullnessPredicate) { - Assertions.assertFalse(predicate.isNull().isNegated()); - } - } - } - - @Test - public void testContainsPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.CONTAINS), criteriaBuilder, root); - testLikePredicate(predicateTest, "%" + TEST_VALUE + "%"); - } - - @Test - public void testNotContainsPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.NOT_CONTAINS), criteriaBuilder, root); - Assertions.assertInstanceOf(SqmJunctionPredicate.class, predicateTest); - - final SqmJunctionPredicate sqmJunctionPredicate = ((SqmJunctionPredicate) predicateTest); - for (final SqmPredicate predicate : sqmJunctionPredicate.getPredicates()) { - Assertions.assertTrue(predicate instanceof SqmLikePredicate || predicate instanceof SqmNullnessPredicate); - if (predicate instanceof SqmLikePredicate) { - Assertions.assertTrue(predicate.isNegated()); - Assertions.assertEquals("%" + TEST_VALUE + "%", ((SqmLikePredicate) predicate).getPattern().toHqlString()); - } else if (predicate instanceof SqmNullnessPredicate) { - Assertions.assertFalse(predicate.isNull().isNegated()); - } - } - } - - @Test - public void testStartWithPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.STARTS_WITH), criteriaBuilder, root); - testLikePredicate(predicateTest, TEST_VALUE + "%"); - } - - @Test - public void testEndWithPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.ENDS_WITH), criteriaBuilder, root); - testLikePredicate(predicateTest, "%" + TEST_VALUE); - } - - @Test - public void testEmptyPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.EMPTY), criteriaBuilder, root); - Assertions.assertInstanceOf(SqmNullnessPredicate.class, predicateTest); - Assertions.assertFalse(predicateTest.isNull().isNegated()); - } - - @Test - public void testNotEmptyPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.NOT_EMPTY), criteriaBuilder, root); - Assertions.assertInstanceOf(SqmNullnessPredicate.class, predicateTest); - Assertions.assertTrue(predicateTest.isNotNull().isNegated()); - } - - @Test - public void testGreaterPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.GREATER), criteriaBuilder, root); - Assertions.assertEquals(ComparisonOperator.GREATER_THAN, ((SqmComparisonPredicate) predicateTest).getSqmOperator()); - Assertions.assertEquals(TEST_DATE_VALUE, ((SqmComparisonPredicate) predicateTest).getRightHandExpression().toHqlString()); - } - - @Test - public void testLesserPredicate() { - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(prepareDummyFilterRequest(FilterConditionOperator.LESSER), criteriaBuilder, root); - Assertions.assertEquals(ComparisonOperator.LESS_THAN, ((SqmComparisonPredicate) predicateTest).getSqmOperator()); - Assertions.assertEquals(TEST_DATE_VALUE, ((SqmComparisonPredicate) predicateTest).getRightHandExpression().toHqlString()); - } - - @Test - public void testOCSPValidation() { - testVerifications(SearchableFields.OCSP_VALIDATION, FilterConditionOperator.EQUALS, CertificateValidationStatus.VALID); - } - - @Test - public void testSignatureValidation() { - testVerifications(SearchableFields.SIGNATURE_VALIDATION, FilterConditionOperator.NOT_EQUALS, CertificateValidationStatus.FAILED); - } - - @Test - public void testCRLValidation() { - testVerifications(SearchableFields.CRL_VALIDATION, FilterConditionOperator.EQUALS, CertificateValidationStatus.EXPIRED); - } - - @Test - public void testReplaceSearchCondition() { - rootCryptoKeyItem = criteriaQuery.from(CryptographicKeyItem.class); - final SearchFilterRequestDTODummy searchFilterRequestDtoDummy = prepareDummyFilterRequest(FilterConditionOperator.EQUALS); - searchFilterRequestDtoDummy.setFieldTest(SearchableFields.CKI_USAGE); - searchFilterRequestDtoDummy.setValueTest("sign"); - - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(searchFilterRequestDtoDummy, criteriaBuilder, rootCryptoKeyItem); - Assertions.assertInstanceOf(SqmLikePredicate.class, predicateTest); - - final String predicateHqlString = ((SqmLikePredicate) predicateTest).getPattern().toHqlString(); - Assertions.assertTrue(predicateHqlString.startsWith("%")); - Assertions.assertTrue(predicateHqlString.endsWith("%")); - } - - - @Test - public void testFilterMetaOnly() { - final List testSearchableFieldsList = new ArrayList<>(); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.COMMON_NAME.name(), AttributeContentType.STRING, AttributeType.META)); - - List testFilters = new ArrayList<>(); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.META, SearchableFields.COMMON_NAME, AttributeContentType.STRING, FilterConditionOperator.EQUALS, "test")); - - final Sql2PredicateConverter.CriteriaQueryDataObject criteriaQueryDataObject - = Sql2PredicateConverter.prepareQueryToSearchIntoAttributes(testSearchableFieldsList, testFilters, criteriaBuilder, Resource.CERTIFICATE); - Assertions.assertEquals(1, criteriaQueryDataObject.getPredicate().getExpressions().size()); - - } - - - @Test - public void testFilterCustomAttrOnly() { - - final List testSearchableFieldsList = new ArrayList<>(); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.COMMON_NAME.name(), AttributeContentType.STRING, AttributeType.META)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.NOT_AFTER.name(), AttributeContentType.DATE, AttributeType.META)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.SERIAL_NUMBER.name(), AttributeContentType.STRING, AttributeType.CUSTOM)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.CKI_LENGTH.name(), AttributeContentType.INTEGER, AttributeType.CUSTOM)); - - List testFilters = new ArrayList<>(); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.SUBJECTDN, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.CUSTOM, SearchableFields.SERIAL_NUMBER, AttributeContentType.STRING, FilterConditionOperator.EQUALS, "test")); - - final Sql2PredicateConverter.CriteriaQueryDataObject criteriaQueryDataObject - = Sql2PredicateConverter.prepareQueryToSearchIntoAttributes(testSearchableFieldsList, testFilters, criteriaBuilder, Resource.CERTIFICATE); - Assertions.assertEquals(1, criteriaQueryDataObject.getPredicate().getExpressions().size()); - - - List testFilters2 = new ArrayList<>(); - testFilters2.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); - testFilters2.add(new SearchFilterRequestDTODummy(FilterFieldSource.CUSTOM, SearchableFields.CKI_LENGTH, AttributeContentType.INTEGER, FilterConditionOperator.EQUALS, 1)); - testFilters2.add(new SearchFilterRequestDTODummy(FilterFieldSource.CUSTOM, SearchableFields.SERIAL_NUMBER, AttributeContentType.STRING, FilterConditionOperator.EQUALS, "test")); - final Sql2PredicateConverter.CriteriaQueryDataObject criteriaQueryDataObject2 - = Sql2PredicateConverter.prepareQueryToSearchIntoAttributes(testSearchableFieldsList, testFilters2, criteriaBuilder, Resource.CERTIFICATE); - Assertions.assertEquals(2, criteriaQueryDataObject2.getPredicate().getExpressions().size()); - - } - - @Test - public void testFilterNoMetaOrCustomAttr() { - - final List testSearchableFieldsList = new ArrayList<>(); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.COMMON_NAME.name(), AttributeContentType.STRING, AttributeType.META)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.NOT_AFTER.name(), AttributeContentType.DATE, AttributeType.META)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.SERIAL_NUMBER.name(), AttributeContentType.STRING, AttributeType.CUSTOM)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.CKI_LENGTH.name(), AttributeContentType.INTEGER, AttributeType.META)); - - List testFilters = new ArrayList<>(); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.SUBJECTDN, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); - - final Sql2PredicateConverter.CriteriaQueryDataObject criteriaQueryDataObject - = Sql2PredicateConverter.prepareQueryToSearchIntoAttributes(testSearchableFieldsList, testFilters, criteriaBuilder, Resource.CERTIFICATE); - Assertions.assertEquals(0, criteriaQueryDataObject.getPredicate().getExpressions().size()); - } - - @Test - public void testFilterNoMetaOrCustomAttrWithCorrectAttrContentType() { - - final List testSearchableFieldsList = new ArrayList<>(); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.COMMON_NAME.name(), AttributeContentType.STRING, AttributeType.META)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.NOT_AFTER.name(), AttributeContentType.DATE, AttributeType.META)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.SERIAL_NUMBER.name(), AttributeContentType.STRING, AttributeType.CUSTOM)); - testSearchableFieldsList.add(new SearchFieldObject(SearchableFields.CKI_LENGTH.name(), AttributeContentType.INTEGER, AttributeType.META)); - - List testFilters = new ArrayList<>(); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.SUBJECTDN, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.META, SearchableFields.CKI_LENGTH, AttributeContentType.STRING, FilterConditionOperator.EQUALS, 1)); - testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.CUSTOM, SearchableFields.SERIAL_NUMBER, AttributeContentType.INTEGER, FilterConditionOperator.EQUALS, "test")); - - final Sql2PredicateConverter.CriteriaQueryDataObject criteriaQueryDataObject - = Sql2PredicateConverter.prepareQueryToSearchIntoAttributes(testSearchableFieldsList, testFilters, criteriaBuilder, Resource.CERTIFICATE); - Assertions.assertEquals(0, criteriaQueryDataObject.getPredicate().getExpressions().size()); - } - - - private void testLikePredicate(final Predicate predicate, final String value) { - Assertions.assertInstanceOf(SqmLikePredicate.class, predicate); - Assertions.assertEquals(value, ((SqmLikePredicate) predicate).getPattern().toHqlString()); - } - - private void testVerifications(final SearchableFields fieldTest, final FilterConditionOperator condition, final CertificateValidationStatus certificateValidationCheckStatus) { - final SearchFilterRequestDTODummy searchFilterRequestDTODummy - = new SearchFilterRequestDTODummy(fieldTest, condition, certificateValidationCheckStatus.getCode()); - final Predicate predicateTest = Sql2PredicateConverter.mapSearchFilter2Predicate(searchFilterRequestDTODummy, criteriaBuilder, root); - Assertions.assertInstanceOf(SqmLikePredicate.class, predicateTest); - - final String hqlString = ((SqmLikePredicate) predicateTest).getPattern().toHqlString(); - Assertions.assertTrue(hqlString.contains(TEST_VERIFICATION_TEXT.replace("%STATUS%", certificateValidationCheckStatus.getCode()))); - Assertions.assertTrue(hqlString.startsWith("%")); - Assertions.assertTrue(hqlString.endsWith("%")); - } - - - private SearchFilterRequestDTODummy prepareDummyFilterRequest(final FilterConditionOperator condition) { - SearchFilterRequestDTODummy dummy = null; - switch (condition) { - case EQUALS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.EQUALS, TEST_VALUE); - case NOT_EQUALS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.NOT_EQUALS, TEST_VALUE); - case CONTAINS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.CONTAINS, TEST_VALUE); - case NOT_CONTAINS -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.NOT_CONTAINS, TEST_VALUE); - case STARTS_WITH -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.STARTS_WITH, TEST_VALUE); - case ENDS_WITH -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.ENDS_WITH, TEST_VALUE); - case EMPTY -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.EMPTY, TEST_VALUE); - case NOT_EMPTY -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.COMMON_NAME, FilterConditionOperator.NOT_EMPTY, TEST_VALUE); - case GREATER -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.NOT_AFTER, FilterConditionOperator.GREATER, TEST_DATE_VALUE); - case LESSER -> - dummy = new SearchFilterRequestDTODummy(SearchableFields.NOT_BEFORE, FilterConditionOperator.LESSER, TEST_DATE_VALUE); - } - return dummy; - } - - -} - -class SearchFilterRequestDTODummy extends SearchFilterRequestDto { - - private SearchableFields fieldTest; - private FilterConditionOperator conditionTest; - private Serializable valueTest; - - private FilterFieldSource filterFieldSource; - - private String fieldIdentifier; - - public SearchFilterRequestDTODummy(SearchableFields fieldTest, FilterConditionOperator conditionTest, Serializable valueTest) { - this.fieldTest = fieldTest; - this.conditionTest = conditionTest; - this.valueTest = valueTest; - this.fieldIdentifier = fieldTest.name(); - } - - public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, SearchableFields fieldTest, FilterConditionOperator conditionTest, Serializable valueTest) { - this.filterFieldSource = filterFieldSource; - this.fieldTest = fieldTest; - this.conditionTest = conditionTest; - this.valueTest = valueTest; - this.fieldIdentifier = fieldTest.name(); - } - - public SearchFilterRequestDTODummy(FilterFieldSource filterFieldSource, SearchableFields fieldTest, AttributeContentType attributeContentType, FilterConditionOperator conditionTest, Serializable valueTest) { - this.filterFieldSource = filterFieldSource; - this.fieldTest = fieldTest; - this.conditionTest = conditionTest; - this.valueTest = valueTest; - this.fieldIdentifier = fieldTest.name() + "|" + attributeContentType.name(); - } - - public SearchableFields getField() { - return fieldTest; - } - - public FilterConditionOperator getCondition() { - return conditionTest; - } - - public Serializable getValue() { - return valueTest; - } - - public void setFieldTest(SearchableFields fieldTest) { - this.fieldTest = fieldTest; - this.fieldIdentifier = fieldTest.name(); - } - - public void setConditionTest(FilterConditionOperator conditionTest) { - this.conditionTest = conditionTest; - } - - public void setValueTest(Serializable valueTest) { - this.valueTest = valueTest; - } - - @Override - public String getFieldIdentifier() { - return fieldIdentifier; - } - - public void setSearchGroup(FilterFieldSource filterFieldSource) { - this.filterFieldSource = filterFieldSource; - } - - @Override - public FilterFieldSource getFieldSource() { - return filterFieldSource; - } -} From 847ffbab9ae00ea98b9ebfddc54872429b929cbc Mon Sep 17 00:00:00 2001 From: lubomirw <76479559+lubomirw@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:00:55 +0200 Subject: [PATCH 02/12] Fix filtering on attributes of non string content types (#857) --- .../core/util/FilterPredicatesBuilder.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java b/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java index 1528e1cd7..a76c7dd25 100644 --- a/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java +++ b/src/main/java/com/czertainly/core/util/FilterPredicatesBuilder.java @@ -1,5 +1,6 @@ package com.czertainly.core.util; +import com.czertainly.api.exception.ValidationException; import com.czertainly.api.model.client.certificate.SearchFilterRequestDto; import com.czertainly.api.model.common.attribute.v2.content.AttributeContentType; import com.czertainly.api.model.common.enums.IPlatformEnum; @@ -23,10 +24,18 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.*; public class FilterPredicatesBuilder { + + private FilterPredicatesBuilder() { + throw new IllegalStateException("Static utility class"); + } + + private static final List castedAttributeContentData = List.of(AttributeContentType.INTEGER, AttributeContentType.FLOAT, AttributeContentType.DATE, AttributeContentType.TIME, AttributeContentType.DATETIME); + public static Predicate getFiltersPredicate(final CriteriaBuilder criteriaBuilder, final CriteriaQuery query, final Root root, final List filterDtos) { Map joinedAssociations = new HashMap<>(); @@ -55,11 +64,6 @@ private static Predicate getAttributeFilterPredicate(final CriteriaBuilder c final String attributeName = fieldIdentifier[0]; final boolean isNotExistCondition = List.of(FilterConditionOperator.NOT_EQUALS, FilterConditionOperator.NOT_CONTAINS, FilterConditionOperator.EMPTY).contains(filterDto.getCondition()); - Expression expression = criteriaBuilder.function("jsonb_extract_path_text", String.class, joinContentItem.get(AttributeContentItem_.json), criteriaBuilder.literal(contentType.isFilterByData() ? "data" : "reference")); - if (contentType.getDataJavaClass() != null && contentType.getDataJavaClass() != String.class) { - expression = expression.as(contentType.getDataJavaClass()); - } - List predicates = new ArrayList<>(); predicates.add(criteriaBuilder.equal(joinDefinition.get(AttributeDefinition_.type), filterDto.getFieldSource().getAttributeType())); predicates.add(criteriaBuilder.equal(joinDefinition.get(AttributeDefinition_.contentType), contentType)); @@ -67,8 +71,17 @@ private static Predicate getAttributeFilterPredicate(final CriteriaBuilder c predicates.add(criteriaBuilder.equal(subqueryRoot.get(AttributeContent2Object_.objectType), resource)); predicates.add(criteriaBuilder.equal(subqueryRoot.get(AttributeContent2Object_.objectUuid), root.get(UniquelyIdentified_.uuid.getName()))); - Predicate conditionPredicate = getAttributeFilterConditionPredicate(criteriaBuilder, filterDto, expression, contentType); - if (conditionPredicate != null) { + if (filterDto.getCondition() != FilterConditionOperator.EMPTY && filterDto.getCondition() != FilterConditionOperator.NOT_EMPTY) { + Expression attributeContentExpression = criteriaBuilder.function("jsonb_extract_path_text", String.class, joinContentItem.get(AttributeContentItem_.json), criteriaBuilder.literal(contentType.isFilterByData() ? "data" : "reference")); + CriteriaBuilder.SimpleCase contentTypeCaseExpression = criteriaBuilder.selectCase(joinDefinition.get(AttributeDefinition_.contentType)); + + if (castedAttributeContentData.contains(contentType)) { + contentTypeCaseExpression.when(contentType, attributeContentExpression.as(contentType.getContentDataClass())).otherwise(criteriaBuilder.nullLiteral(contentType.getContentDataClass())); + } else { + contentTypeCaseExpression.when(contentType, attributeContentExpression).otherwise(criteriaBuilder.nullLiteral(String.class)); + } + + Predicate conditionPredicate = getAttributeFilterConditionPredicate(criteriaBuilder, filterDto, contentTypeCaseExpression, contentType); predicates.add(conditionPredicate); } @@ -110,13 +123,17 @@ private static List prepareAttributeFilterValues(final SearchFilterReque for (Object value : filterValues) { String stringValue = value.toString(); Object preparedValue = switch (contentType) { - case BOOLEAN -> Boolean.parseBoolean(stringValue); + case BOOLEAN -> Boolean.parseBoolean(stringValue) ? "true" : "false"; case INTEGER -> Integer.parseInt(stringValue); case FLOAT -> Float.parseFloat(stringValue); case DATE -> LocalDate.parse(stringValue); case TIME -> LocalTime.parse(stringValue); - case DATETIME -> - LocalDateTime.parse(stringValue, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); + case DATETIME -> { + if (!stringValue.contains("+") && !stringValue.endsWith("Z")) { + stringValue += "Z"; + } + yield ZonedDateTime.parse(stringValue, DateTimeFormatter.ofPattern("[yyyy-MM-dd'T'HH:mm:ss.SSSXXX][yyyy-MM-dd'T'HH:mm:ssXXX][yyyy-MM-dd'T'HH:mmXXX]")); + } case null, default -> stringValue; }; @@ -191,12 +208,12 @@ private static Predicate getPropertyFilterPredicate(final CriteriaBuilder cr predicate = criteriaBuilder.lessThan(expression, (Expression) criteriaBuilder.literal(filterValues.getFirst())); case LESSER_OR_EQUAL -> predicate = criteriaBuilder.lessThanOrEqualTo(expression, (Expression) criteriaBuilder.literal(filterValues.getFirst())); + default -> throw new ValidationException("Unexpected value: " + conditionOperator); } return predicate; } private static From getJoinedAssociation(Root root, Map joinedAssociations, FilterField filterField) { - // join associations From from = root; From joinedAssociation; String associationFullPath = null; @@ -248,7 +265,7 @@ private static List preparePropertyFilterValues(final SearchFilterReques } else if (filterField.getType() == SearchFieldTypeEnum.DATE) { preparedFilterValue = LocalDate.parse(stringValue); } else if (filterField.getType() == SearchFieldTypeEnum.DATETIME) { - preparedFilterValue = LocalDateTime.parse(stringValue, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")); + preparedFilterValue = LocalDateTime.parse(stringValue, DateTimeFormatter.ofPattern("[yyyy-MM-dd'T'HH:mm:ss.SSSXXX][yyyy-MM-dd'T'HH:mm:ssXXX][yyyy-MM-dd'T'HH:mmXXX]")); } else { preparedFilterValue = stringValue; } From 7549345c1f492acbd359d78dbc446338fad4c487 Mon Sep 17 00:00:00 2001 From: 3keyroman <46850604+3keyroman@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:24:20 +0200 Subject: [PATCH 03/12] Update build action to update snapshots (#858) --- .github/workflows/build.yml | 2 +- prebuild_image_script | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d4ad42cd..6bb8cce4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,4 +58,4 @@ jobs: - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - run: mvn -B verify + run: mvn -B -U verify diff --git a/prebuild_image_script b/prebuild_image_script index 588526c56..c0762d3ea 100755 --- a/prebuild_image_script +++ b/prebuild_image_script @@ -16,9 +16,9 @@ docker build -f Dockerfile-pre -t prebuild . echo "MVN Build" if [[ "$OSTYPE" == "darwin"* ]]; then echo "MacOS detected, using TESTCONTAINERS_HOST_OVERRIDE" - docker run -e TESTCONTAINERS_HOST_OVERRIDE=docker.for.mac.host.internal -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml clean package + docker run -e TESTCONTAINERS_HOST_OVERRIDE=docker.for.mac.host.internal -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml -U clean package else - docker run -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml clean package + docker run -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml -U clean package fi echo "Starting czertainlycont" From 71a1290889c3fed27a25bb384fcbdc7f9cbea353 Mon Sep 17 00:00:00 2001 From: klaraf755 <80590912+klaraf755@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:17:08 +0200 Subject: [PATCH 04/12] Add Subject type property to certificate and add filter for it (#860) --- .../core/dao/entity/Certificate.java | 7 ++-- .../czertainly/core/enums/FilterField.java | 3 +- .../service/impl/CertificateServiceImpl.java | 3 +- .../czertainly/core/util/CertificateUtil.java | 32 +++++++++------ .../certificate/X509CertificateValidator.java | 37 +++++++----------- ...09121200__add_certificate_subject_type.sql | 10 +++++ .../util/FilterPredicatesBuilderTest.java | 39 +++++++++++-------- 7 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 src/main/resources/db/migration/V202409121200__add_certificate_subject_type.sql diff --git a/src/main/java/com/czertainly/core/dao/entity/Certificate.java b/src/main/java/com/czertainly/core/dao/entity/Certificate.java index b2f98ac4a..43449c4d4 100644 --- a/src/main/java/com/czertainly/core/dao/entity/Certificate.java +++ b/src/main/java/com/czertainly/core/dao/entity/Certificate.java @@ -90,8 +90,9 @@ public class Certificate extends UniquelyIdentifiedAndAudited implements Seriali @Column(name = "key_usage") private String keyUsage; - @Column(name = "basic_constraints") - private String basicConstraints; + @Column(name = "subject_type", nullable = false) + @Enumerated(EnumType.STRING) + private CertificateSubjectType subjectType = CertificateSubjectType.END_ENTITY; @Column(name = "state") @Enumerated(EnumType.STRING) @@ -211,7 +212,7 @@ public CertificateDetailDto mapToDto() { dto.setIssuerDn(issuerDn); dto.setNotBefore(notBefore); dto.setNotAfter(notAfter); - dto.setBasicConstraints(basicConstraints); + dto.setSubjectType(subjectType); dto.setExtendedKeyUsage(MetaDefinitions.deserializeArrayString(extendedKeyUsage)); dto.setKeyUsage(MetaDefinitions.deserializeArrayString(keyUsage)); dto.setFingerprint(fingerprint); diff --git a/src/main/java/com/czertainly/core/enums/FilterField.java b/src/main/java/com/czertainly/core/enums/FilterField.java index f26ecc73e..6a22f74b2 100644 --- a/src/main/java/com/czertainly/core/enums/FilterField.java +++ b/src/main/java/com/czertainly/core/enums/FilterField.java @@ -6,6 +6,7 @@ import com.czertainly.api.model.common.enums.cryptography.KeyType; import com.czertainly.api.model.core.auth.Resource; import com.czertainly.api.model.core.certificate.CertificateState; +import com.czertainly.api.model.core.certificate.CertificateSubjectType; import com.czertainly.api.model.core.certificate.CertificateValidationStatus; import com.czertainly.api.model.core.compliance.ComplianceStatus; import com.czertainly.api.model.core.cryptography.key.KeyState; @@ -37,7 +38,7 @@ public enum FilterField { PUBLIC_KEY_ALGORITHM(Resource.CERTIFICATE, null, null, Certificate_.publicKeyAlgorithm, "Public Key Algorithm", SearchFieldTypeEnum.LIST), KEY_SIZE(Resource.CERTIFICATE, null, null, Certificate_.keySize, "Key Size", SearchFieldTypeEnum.LIST), KEY_USAGE(Resource.CERTIFICATE, null, null, Certificate_.keyUsage, "Key Usage", SearchFieldTypeEnum.LIST), - BASIC_CONSTRAINTS(Resource.CERTIFICATE, null, null, Certificate_.basicConstraints, "Basic Constraints", SearchFieldTypeEnum.LIST), + SUBJECT_TYPE(Resource.CERTIFICATE, null, null, Certificate_.subjectType, "Subject Type", SearchFieldTypeEnum.LIST, CertificateSubjectType.class), SUBJECT_ALTERNATIVE_NAMES(Resource.CERTIFICATE, null, null, Certificate_.subjectAlternativeNames, "Subject Alternative Name", SearchFieldTypeEnum.STRING), SUBJECTDN(Resource.CERTIFICATE, null, null, Certificate_.subjectDn, "Subject DN", SearchFieldTypeEnum.STRING), ISSUERDN(Resource.CERTIFICATE, null, null, Certificate_.issuerDn, "Issuer DN", SearchFieldTypeEnum.STRING), diff --git a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java index bd38290b3..4967e0dd6 100644 --- a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java @@ -447,6 +447,7 @@ public List getSearchableFieldInformationByGroup() { SearchHelper.prepareSearch(FilterField.KEY_SIZE, new ArrayList<>(certificateRepository.findDistinctKeySize())), SearchHelper.prepareSearch(FilterField.KEY_USAGE, serializedListOfStringToListOfObject(certificateRepository.findDistinctKeyUsage())), SearchHelper.prepareSearch(FilterField.PRIVATE_KEY), + SearchHelper.prepareSearch(FilterField.SUBJECT_TYPE, Arrays.stream(CertificateSubjectType.values()).map(CertificateSubjectType::getCode).toList()), SearchHelper.prepareSearch(FilterField.TRUSTED_CA) ); @@ -1061,7 +1062,7 @@ public StatisticsDto addCertificateStatistics(SecurityFilter filter, StatisticsD return null; }, () -> { - dto.setCertificateStatByBasicConstraints(certificateRepository.countGroupedUsingSecurityFilter(filter, null, Certificate_.basicConstraints, null)); + dto.setCertificateStatBySubjectType(certificateRepository.countGroupedUsingSecurityFilter(filter, null, Certificate_.subjectType, null)); return null; }, () -> { diff --git a/src/main/java/com/czertainly/core/util/CertificateUtil.java b/src/main/java/com/czertainly/core/util/CertificateUtil.java index d1c0354c1..3bc6d45a3 100644 --- a/src/main/java/com/czertainly/core/util/CertificateUtil.java +++ b/src/main/java/com/czertainly/core/util/CertificateUtil.java @@ -5,10 +5,7 @@ import com.czertainly.api.exception.ValidationException; import com.czertainly.api.model.common.enums.cryptography.KeyAlgorithm; import com.czertainly.api.model.common.enums.cryptography.KeyType; -import com.czertainly.api.model.core.certificate.CertificateState; -import com.czertainly.api.model.core.certificate.CertificateType; -import com.czertainly.api.model.core.certificate.CertificateValidationStatus; -import com.czertainly.api.model.core.certificate.X500RdnType; +import com.czertainly.api.model.core.certificate.*; import com.czertainly.api.model.core.compliance.ComplianceStatus; import com.czertainly.api.model.core.cryptography.key.KeyState; import com.czertainly.api.model.core.cryptography.key.KeyUsage; @@ -22,7 +19,6 @@ import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.DLTaggedObject; import org.bouncycastle.asn1.cmp.CMPCertificate; -import org.bouncycastle.asn1.crmf.CertTemplate; import org.bouncycastle.asn1.pkcs.Attribute; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x500.RDN; @@ -149,11 +145,24 @@ public static boolean isKeyUsagePresent(boolean[] keyUsage, String keyUsageName) return keyUsageIndex != -1 && keyUsage[keyUsageIndex]; } - public static String getBasicConstraint(int bcValue) { + public static CertificateSubjectType getCertificateSubjectType(X509Certificate certificate, boolean subjectDnEqualsIssuerDn) { + boolean selfSigned = false; + if (subjectDnEqualsIssuerDn) { + try { + certificate.verify(certificate.getPublicKey()); + // Certificate is self-signed only if the signature of self has been successfully verified + selfSigned = true; + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | + SignatureException ignored) { + } + } + + int bcValue = certificate.getBasicConstraints(); + // Certificate is Certificate Authority if Basic Constraint Value is positive, otherwise it is End Entity if (bcValue >= 0) { - return "Subject Type=CA"; + return selfSigned ? CertificateSubjectType.ROOT_CA : CertificateSubjectType.INTERMEDIATE_CA; } else { - return "Subject Type=End Entity"; + return selfSigned ? CertificateSubjectType.SELF_SIGNED_END_ENTITY : CertificateSubjectType.END_ENTITY; } } @@ -404,10 +413,11 @@ public static void prepareIssuedCertificate(Certificate modal, X509Certificate c modal.setKeyUsage( MetaDefinitions.serializeArrayString(CertificateUtil.keyUsageExtractor(certificate.getKeyUsage()))); - String basicConstraints = CertificateUtil.getBasicConstraint(certificate.getBasicConstraints()); - modal.setBasicConstraints(basicConstraints); + CertificateSubjectType subjectType = CertificateUtil.getCertificateSubjectType(certificate, modal.getSubjectDnNormalized().equals(modal.getIssuerDnNormalized())); + modal.setSubjectType(subjectType); + // Set trusted certificate mark either for CA or for self-signed certificate - if (basicConstraints.equals("Subject Type=CA") || Objects.equals(modal.getSubjectDnNormalized(), modal.getIssuerDnNormalized())) + if (subjectType != CertificateSubjectType.END_ENTITY) modal.setTrustedCa(false); } diff --git a/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java b/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java index 8060d5021..f80f98be6 100644 --- a/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java +++ b/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java @@ -1,23 +1,15 @@ package com.czertainly.core.validation.certificate; import com.czertainly.api.exception.ValidationException; -import com.czertainly.api.model.core.certificate.CertificateState; -import com.czertainly.api.model.core.certificate.CertificateValidationCheck; -import com.czertainly.api.model.core.certificate.CertificateValidationCheckDto; -import com.czertainly.api.model.core.certificate.CertificateValidationStatus; +import com.czertainly.api.model.core.certificate.*; import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.Crl; import com.czertainly.core.dao.entity.CrlEntry; import com.czertainly.core.dao.repository.CertificateRepository; -import com.czertainly.core.dao.repository.CrlEntryRepository; -import com.czertainly.core.dao.repository.CrlRepository; import com.czertainly.core.service.CrlService; import com.czertainly.core.util.CertificateUtil; -import com.czertainly.core.util.CrlUtil; -import com.czertainly.core.util.CzertainlyX500NameStyle; import com.czertainly.core.util.OcspUtil; import com.fasterxml.jackson.databind.ObjectMapper; -import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.Extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +17,6 @@ import org.springframework.stereotype.Service; import java.io.IOException; -import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Instant; @@ -74,7 +65,7 @@ public CertificateValidationStatus validateCertificate(Certificate certificate, x509Certificate = CertificateUtil.getX509Certificate(certificateChain.get(i).getCertificateContent().getContent()); boolean isEndCertificate = i == 0; - validationOutput = validatePathCertificate(x509Certificate, x509IssuerCertificate, certificateChain.get(i).getTrustedCa(), previousCertStatus, isCompleteChain, isEndCertificate, certificateChain.get(i).getIssuerDnNormalized()); + validationOutput = validatePathCertificate(x509Certificate, x509IssuerCertificate, certificateChain.get(i).getTrustedCa(), previousCertStatus, isCompleteChain, isEndCertificate, certificateChain.get(i).getSubjectType()); CertificateValidationStatus resultStatus = calculateResultStatus(validationOutput); finalizeValidation(certificateChain.get(i), resultStatus, validationOutput); @@ -86,7 +77,7 @@ public CertificateValidationStatus validateCertificate(Certificate certificate, return previousCertStatus; } - private Map validatePathCertificate(X509Certificate certificate, X509Certificate issuerCertificate, Boolean trustedCa, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain, boolean isEndCertificate, String issuerDn) { + private Map validatePathCertificate(X509Certificate certificate, X509Certificate issuerCertificate, Boolean trustedCa, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain, boolean isEndCertificate, CertificateSubjectType subjectType) { Map validationOutput = initializeValidationOutput(); // check certificate signature @@ -104,24 +95,23 @@ private Map validateP // check certificate issuer DN and if certificate chain is valid // section (a)(4) in https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.3 - validationOutput.put(CertificateValidationCheck.CERTIFICATE_CHAIN, checkCertificateChain(certificate, issuerCertificate, trustedCa, issuerCertificateStatus, isCompleteChain)); + validationOutput.put(CertificateValidationCheck.CERTIFICATE_CHAIN, checkCertificateChain(certificate, issuerCertificate, trustedCa, issuerCertificateStatus, isCompleteChain, subjectType)); // (k) and (l) section in https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.4 - validationOutput.put(CertificateValidationCheck.BASIC_CONSTRAINTS, checkBasicConstraints(certificate, issuerCertificate, isEndCertificate)); + validationOutput.put(CertificateValidationCheck.BASIC_CONSTRAINTS, checkBasicConstraints(certificate, issuerCertificate, isEndCertificate, subjectType)); // (n) section in https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.4 - validationOutput.put(CertificateValidationCheck.KEY_USAGE, checkKeyUsage(certificate)); + validationOutput.put(CertificateValidationCheck.KEY_USAGE, checkKeyUsage(certificate, subjectType)); return validationOutput; } - private CertificateValidationCheckDto checkCertificateChain(X509Certificate certificate, X509Certificate issuerCertificate, Boolean isTrustedCa, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain) { + private CertificateValidationCheckDto checkCertificateChain(X509Certificate certificate, X509Certificate issuerCertificate, Boolean isTrustedCa, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain, CertificateSubjectType subjectType) { if (issuerCertificate == null) { // should be trust anchor (Root CA certificate or self-signed certificate) if (isCompleteChain) { - String certificateType = "self-signed"; - // Check if certificate is CA, for CA certificate is this value always > -1 - if (certificate.getBasicConstraints() > -1) certificateType = "root CA"; + String certificateType = subjectType.getLabel(); + if (Boolean.TRUE.equals(isTrustedCa)) { return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.VALID, "Certificate chain is complete. Certificate is trusted " + certificateType + " certificate."); } else { @@ -288,9 +278,9 @@ private CertificateValidationCheckDto checkCrlRevocationStatus(X509Certificate c } - private CertificateValidationCheckDto checkBasicConstraints(X509Certificate certificate, X509Certificate issuerCertificate, boolean isEndCertificate) { + private CertificateValidationCheckDto checkBasicConstraints(X509Certificate certificate, X509Certificate issuerCertificate, boolean isEndCertificate, CertificateSubjectType subjectType) { int pathLenConstraint = certificate.getBasicConstraints(); - boolean isCa = pathLenConstraint >= 0; + boolean isCa = subjectType.isCa(); if (!isCa) { if (certificate.getVersion() == 3 && !isEndCertificate) { @@ -305,10 +295,9 @@ private CertificateValidationCheckDto checkBasicConstraints(X509Certificate cert return new CertificateValidationCheckDto(CertificateValidationCheck.BASIC_CONSTRAINTS, CertificateValidationStatus.VALID, "Certificate basic constraints verification successful."); } - private CertificateValidationCheckDto checkKeyUsage(X509Certificate certificate) { - boolean isCa = certificate.getBasicConstraints() >= 0; + private CertificateValidationCheckDto checkKeyUsage(X509Certificate certificate, CertificateSubjectType subjectType) { - if (!isCa) { + if (!subjectType.isCa()) { return new CertificateValidationCheckDto(CertificateValidationCheck.KEY_USAGE, CertificateValidationStatus.NOT_CHECKED, "Certificate is not CA."); } diff --git a/src/main/resources/db/migration/V202409121200__add_certificate_subject_type.sql b/src/main/resources/db/migration/V202409121200__add_certificate_subject_type.sql new file mode 100644 index 000000000..2f55f44ca --- /dev/null +++ b/src/main/resources/db/migration/V202409121200__add_certificate_subject_type.sql @@ -0,0 +1,10 @@ +ALTER TABLE certificate ADD COLUMN subject_type TEXT; + +UPDATE certificate SET subject_type = 'END_ENTITY' WHERE (basic_constraints = 'Subject Type=End Entity' AND (subject_dn_normalized != issuer_dn_normalized)) OR basic_constraints IS NULL; +UPDATE certificate SET subject_type = 'SELF_SIGNED_END_ENTITY' WHERE basic_constraints = 'Subject Type=End Entity' AND (subject_dn_normalized = issuer_dn_normalized); +UPDATE certificate SET subject_type = 'INTERMEDIATE_CA' WHERE basic_constraints = 'Subject Type=CA' AND (subject_dn_normalized != issuer_dn_normalized); +UPDATE certificate SET subject_type = 'ROOT_CA' WHERE basic_constraints = 'Subject Type=CA' AND (subject_dn_normalized = issuer_dn_normalized); + +ALTER TABLE certificate ALTER COLUMN subject_type SET NOT NULL; + +ALTER TABLE certificate DROP COLUMN basic_constraints; diff --git a/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java b/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java index 2954dabd7..01e3a2c68 100644 --- a/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java +++ b/src/test/java/com/czertainly/core/util/FilterPredicatesBuilderTest.java @@ -12,6 +12,7 @@ import com.czertainly.api.model.common.enums.cryptography.KeyType; import com.czertainly.api.model.core.auth.Resource; import com.czertainly.api.model.core.certificate.CertificateState; +import com.czertainly.api.model.core.certificate.CertificateSubjectType; import com.czertainly.api.model.core.search.FilterConditionOperator; import com.czertainly.api.model.core.search.FilterFieldSource; import com.czertainly.core.attribute.engine.AttributeEngine; @@ -110,8 +111,11 @@ static void authServiceProperties(DynamicPropertyRegistry registry) { public void prepare() throws AlreadyExistException, AttributeException, NotFoundException { certificate1 = new Certificate(); + certificate1.setSubjectType(CertificateSubjectType.ROOT_CA); certificate2 = new Certificate(); + certificate2.setSubjectType(CertificateSubjectType.END_ENTITY); certificate3 = new Certificate(); + certificate3.setSubjectType(CertificateSubjectType.END_ENTITY); certificateRepository.saveAll(List.of(certificate1, certificate2, certificate3)); CustomAttributeDefinitionDetailDto intAttribute = createCustomAttribute("integer", AttributeContentType.INTEGER); @@ -119,8 +123,8 @@ public void prepare() throws AlreadyExistException, AttributeException, NotFound attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, intAttribute.getName(), List.of(new IntegerAttributeContent("ref", 2))); CustomAttributeDefinitionDetailDto decimalAttribute = createCustomAttribute("decimal", AttributeContentType.FLOAT); - attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, decimalAttribute.getName(), List.of(new FloatAttributeContent("ref", 1.3f))); - attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, decimalAttribute.getName(), List.of(new FloatAttributeContent("ref", 1.33f))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate1.getUuid(), null, decimalAttribute.getName(), List.of(new FloatAttributeContent("ref", 0.5f))); + attributeEngine.updateObjectCustomAttributeContent(Resource.CERTIFICATE, certificate2.getUuid(), null, decimalAttribute.getName(), List.of(new FloatAttributeContent("ref", 1.5f))); CustomAttributeDefinitionDetailDto booleanAttribute = createCustomAttribute("boolean", AttributeContentType.BOOLEAN); @@ -247,7 +251,7 @@ void testLesserPredicate() { } @Test - void testCombinedFilters() { + void testCombinedFiltersPredicate() { List testFilters = new ArrayList<>(); testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, FilterField.SUBJECTDN, FilterConditionOperator.EQUALS, "test")); testFilters.add(new SearchFilterRequestDTODummy(FilterFieldSource.PROPERTY, FilterField.COMMON_NAME, FilterConditionOperator.EQUALS, "test")); @@ -263,7 +267,16 @@ void testCombinedFilters() { } @Test - @Disabled("In process of being fixed") + void testEnumPropertyFilter() { + SearchRequestDto searchRequestDto = new SearchRequestDto(); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.SUBJECT_TYPE.name(), FilterConditionOperator.EQUALS, CertificateSubjectType.ROOT_CA.getCode()))); + Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.PROPERTY, FilterField.SUBJECT_TYPE.name(), FilterConditionOperator.NOT_EQUALS, CertificateSubjectType.ROOT_CA.getCode()))); + Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); + } + + @Test void testIntegerAttribute() { final String ATTR_IDENTIFIER = "integer|INTEGER"; @@ -301,32 +314,30 @@ void testIntegerAttribute() { } @Test - @Disabled("In process of being fixed, greater/lesser or equal not working") void testDecimalAttribute() { - final String ATTR_IDENTIFIER = "decimal|FLOAT"; SearchRequestDto searchRequestDto = new SearchRequestDto(); - searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, 1.3f))); + searchRequestDto.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.EQUALS, 0.5f))); Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto))); SearchRequestDto searchRequestDto2 = new SearchRequestDto(); - searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, 1.3f))); + searchRequestDto2.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.NOT_EQUALS, 0.5f))); Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate3.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto2))); SearchRequestDto searchRequestDto3 = new SearchRequestDto(); - searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, 1.3f))); + searchRequestDto3.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER, 0.5f))); Assertions.assertEquals(Set.of(certificate2.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto3))); SearchRequestDto searchRequestDto4 = new SearchRequestDto(); - searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, 1.3f))); + searchRequestDto4.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.GREATER_OR_EQUAL, 0.5f))); Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto4))); SearchRequestDto searchRequestDto5 = new SearchRequestDto(); - searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, 1.33f))); + searchRequestDto5.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER, 1.5f))); Assertions.assertEquals(Set.of(certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto5))); SearchRequestDto searchRequestDto6 = new SearchRequestDto(); - searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, 1.33f))); + searchRequestDto6.setFilters(List.of(new SearchFilterRequestDto(FilterFieldSource.CUSTOM, ATTR_IDENTIFIER, FilterConditionOperator.LESSER_OR_EQUAL, 1.5f))); Assertions.assertEquals(Set.of(certificate2.getUuid(), certificate1.getUuid()), getUuidsFromListCertificatesResponse(certificateService.listCertificates(new SecurityFilter(), searchRequestDto6))); SearchRequestDto searchRequestDto7 = new SearchRequestDto(); @@ -339,7 +350,6 @@ void testDecimalAttribute() { } @Test - @Disabled("In process of being fixed") void testBooleanAttribute() throws AlreadyExistException, AttributeException, NotFoundException { final String ATTR_IDENTIFIER = "boolean|BOOLEAN"; SearchRequestDto searchRequestDto = new SearchRequestDto(); @@ -398,7 +408,6 @@ void testStringAttribute() { } @Test - @Disabled("In process of being fixed") void testDateAttribute() { final String ATTR_IDENTIFIER = "date|DATE"; @@ -437,7 +446,6 @@ void testDateAttribute() { } @Test - @Disabled("In process of being fixed") void testDateTimeAttribute() { final String ATTR_IDENTIFIER = "datetime|DATETIME"; @@ -476,7 +484,6 @@ void testDateTimeAttribute() { } @Test - @Disabled("In process of being fixed") void testTimeAttribute() { final String ATTR_IDENTIFIER = "time|TIME"; From a16d7383a693b4bc35920ce3a32497cfd631f40e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:55:56 +0200 Subject: [PATCH 05/12] Update dependency io.opentelemetry:opentelemetry-bom to v1.42.1 (#832) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 74da60719..4f8ebafb2 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ io.opentelemetry opentelemetry-bom - 1.41.0 + 1.42.1 pom import From 1875fab0e8e804a9532f7daf7f2743612921b614 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:00:16 +0200 Subject: [PATCH 06/12] Update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom to v2.8.0 (#863) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4f8ebafb2..91e339bfa 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ io.opentelemetry.instrumentation opentelemetry-instrumentation-bom - 2.7.0 + 2.8.0 pom import From d4a872bd124e0bfca36ffe29ca9c78bda76f8fcf Mon Sep 17 00:00:00 2001 From: lubomirw <76479559+lubomirw@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:05:17 +0200 Subject: [PATCH 07/12] Fix editing of scheduled job CRON expression and return correct response (#870) --- .../service/impl/SchedulerServiceImpl.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java index cad1f31ec..c9a799f41 100644 --- a/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java @@ -13,6 +13,7 @@ import com.czertainly.api.model.scheduler.SchedulerJobDto; import com.czertainly.api.model.scheduler.SchedulerRequestDto; import com.czertainly.api.model.scheduler.UpdateScheduledJob; +import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.ScheduledJob; import com.czertainly.core.dao.entity.ScheduledJobHistory; import com.czertainly.core.dao.repository.ScheduledJobHistoryRepository; @@ -90,7 +91,7 @@ public void deleteScheduledJob(final String uuid) { throw new ValidationException(ValidationError.create("Unable to delete system job.")); } - if(scheduledJobHistoryRepository.existsByScheduledJobUuid(UUID.fromString(uuid))) { + if (scheduledJobHistoryRepository.existsByScheduledJobUuid(UUID.fromString(uuid))) { logger.warn("Unable to delete job with existing history."); throw new ValidationException(ValidationError.create("Unable to delete job with existing history.")); } @@ -138,22 +139,17 @@ public void disableScheduledJob(final String uuid) throws SchedulerException, No @Override public ScheduledJobDetailDto updateScheduledJob(String uuid, UpdateScheduledJob request) throws NotFoundException, SchedulerException { - Optional scheduledJobOptional = scheduledJobsRepository.findByUuid(SecuredUUID.fromString(uuid)); - if (scheduledJobOptional.isPresent()) { - ScheduledJob scheduledJob = scheduledJobOptional.get(); - scheduledJob.setCronExpression(request.getCronExpression()); - scheduledJobsRepository.save(scheduledJob); - if (scheduledJob.isSystem()) throw new ValidationException("Cannot updated system job."); - SchedulerRequestDto schedulerRequestDto = new SchedulerRequestDto( - new SchedulerJobDto(scheduledJob.getUuid(), scheduledJob.getJobName(), request.getCronExpression(), scheduledJob.getJobClassName()) - ); - schedulerApiClient.updateScheduledJob(schedulerRequestDto); + ScheduledJob scheduledJob = scheduledJobsRepository.findByUuid(SecuredUUID.fromString(uuid)).orElseThrow(() -> new NotFoundException(ScheduledJob.class, uuid)); + if (scheduledJob.isSystem()) throw new ValidationException("Cannot updated system job."); + SchedulerRequestDto schedulerRequestDto = new SchedulerRequestDto( + new SchedulerJobDto(scheduledJob.getUuid(), scheduledJob.getJobName(), request.getCronExpression(), scheduledJob.getJobClassName()) + ); + schedulerApiClient.updateScheduledJob(schedulerRequestDto); - } else { - throw new NotFoundException(ScheduledJob.class, uuid); - } + scheduledJob.setCronExpression(request.getCronExpression()); + scheduledJobsRepository.save(scheduledJob); - return null; + return scheduledJob.mapToDetailDto(scheduledJobHistoryRepository.findTopByScheduledJobUuidOrderByJobExecutionDesc(UUID.fromString(uuid))); } private void changeScheduledJobState(final String uuid, final boolean enabled) throws SchedulerException, NotFoundException { From 69b8d021a9e04245433f11afa85dbb6a55275cf7 Mon Sep 17 00:00:00 2001 From: lubomirw <76479559+lubomirw@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:04:47 +0200 Subject: [PATCH 08/12] Fix transaction handling in scheduled discoveries process (#871) --- .../core/api/web/DiscoveryControllerImpl.java | 34 +- .../core/dao/entity/ScheduledJob.java | 6 +- .../repository/ScheduledJobsRepository.java | 3 +- .../transaction/DiscoveryFinishedEvent.java | 4 +- .../ScheduledJobFinishedEvent.java | 7 + .../messaging/listeners/EventListener.java | 16 +- .../listeners/SchedulerListener.java | 68 +--- .../core/messaging/model/EventMessage.java | 3 +- .../messaging/producers/EventProducer.java | 9 +- .../core/service/DiscoveryService.java | 5 +- .../core/service/SchedulerService.java | 6 + .../service/impl/DiscoveryServiceImpl.java | 377 ++++++++++-------- .../service/impl/SchedulerServiceImpl.java | 195 ++++++++- .../core/tasks/DiscoveryCertificateTask.java | 72 ++-- .../core/tasks/ScheduledJobInfo.java | 11 + .../core/tasks/ScheduledJobTask.java | 19 + .../czertainly/core/tasks/ScheduledTasks.java | 37 -- .../core/tasks/SchedulerJobProcessor.java | 160 -------- .../core/tasks/SystemScheduledJobs.java | 29 ++ .../tasks/UpdateCertificateStatusTask.java | 20 +- .../UpdateIntuneRevocationRequestsTask.java | 42 +- .../core/service/DiscoveryServiceTest.java | 6 +- .../core/service/SchedulerServiceTest.java | 301 ++++++++++++++ 23 files changed, 880 insertions(+), 550 deletions(-) create mode 100644 src/main/java/com/czertainly/core/event/transaction/ScheduledJobFinishedEvent.java create mode 100644 src/main/java/com/czertainly/core/tasks/ScheduledJobInfo.java create mode 100644 src/main/java/com/czertainly/core/tasks/ScheduledJobTask.java delete mode 100644 src/main/java/com/czertainly/core/tasks/ScheduledTasks.java delete mode 100644 src/main/java/com/czertainly/core/tasks/SchedulerJobProcessor.java create mode 100644 src/main/java/com/czertainly/core/tasks/SystemScheduledJobs.java create mode 100644 src/test/java/com/czertainly/core/service/SchedulerServiceTest.java diff --git a/src/main/java/com/czertainly/core/api/web/DiscoveryControllerImpl.java b/src/main/java/com/czertainly/core/api/web/DiscoveryControllerImpl.java index 081393127..150d53a1e 100644 --- a/src/main/java/com/czertainly/core/api/web/DiscoveryControllerImpl.java +++ b/src/main/java/com/czertainly/core/api/web/DiscoveryControllerImpl.java @@ -9,12 +9,12 @@ import com.czertainly.api.model.client.discovery.DiscoveryHistoryDetailDto; import com.czertainly.api.model.common.UuidDto; import com.czertainly.api.model.core.scheduler.ScheduleDiscoveryDto; +import com.czertainly.api.model.core.scheduler.ScheduledJobDetailDto; import com.czertainly.api.model.core.search.SearchFieldDataByGroupDto; -import com.czertainly.core.dao.entity.ScheduledJob; -import com.czertainly.core.dao.repository.ScheduledJobsRepository; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.DiscoveryService; +import com.czertainly.core.service.SchedulerService; import com.czertainly.core.tasks.DiscoveryCertificateTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,9 +36,17 @@ public class DiscoveryControllerImpl implements DiscoveryController { private DiscoveryService discoveryService; - private ScheduledJobsRepository scheduledJobsRepository; + private SchedulerService schedulerService; - private DiscoveryCertificateTask discoveryCertificateTask; + @Autowired + public void setSchedulerService(SchedulerService schedulerService) { + this.schedulerService = schedulerService; + } + + @Autowired + public void setDiscoveryService(DiscoveryService discoveryService) { + this.discoveryService = discoveryService; + } @Override public DiscoveryResponseDto listDiscoveries(final SearchRequestDto request) { @@ -91,7 +99,7 @@ public ResponseEntity scheduleDiscovery(final ScheduleDiscoveryDto scheduleDi jobName = scheduleDiscoveryDto.getJobName(); } - ScheduledJob scheduledJob = discoveryCertificateTask.registerScheduler(jobName, scheduleDiscoveryDto.getCronExpression(), scheduleDiscoveryDto.isOneTime(), scheduleDiscoveryDto.getRequest()); + ScheduledJobDetailDto scheduledJob = schedulerService.registerScheduledJob(DiscoveryCertificateTask.class, jobName, scheduleDiscoveryDto.getCronExpression(), scheduleDiscoveryDto.isOneTime(), scheduleDiscoveryDto.getRequest()); logger.info("Job {} was registered.", jobName); // TODO: construct location URI differently without hardcoded path @@ -120,20 +128,4 @@ public List getSearchableFieldInformation() { return discoveryService.getSearchableFieldInformationByGroup(); } - // SETTERs - - @Autowired - public void setDiscoveryService(DiscoveryService discoveryService) { - this.discoveryService = discoveryService; - } - - @Autowired - public void setScheduledJobsRepository(ScheduledJobsRepository scheduledJobsRepository) { - this.scheduledJobsRepository = scheduledJobsRepository; - } - - @Autowired - public void setDiscoveryCertificateTask(DiscoveryCertificateTask discoveryCertificateTask) { - this.discoveryCertificateTask = discoveryCertificateTask; - } } diff --git a/src/main/java/com/czertainly/core/dao/entity/ScheduledJob.java b/src/main/java/com/czertainly/core/dao/entity/ScheduledJob.java index 77c79def5..b84e88b93 100644 --- a/src/main/java/com/czertainly/core/dao/entity/ScheduledJob.java +++ b/src/main/java/com/czertainly/core/dao/entity/ScheduledJob.java @@ -21,7 +21,7 @@ @RequiredArgsConstructor @Entity @Table(name = "scheduled_job") -public class ScheduledJob extends UniquelyIdentified{ +public class ScheduledJob extends UniquelyIdentified { @Column(name = "job_name") private String jobName; @@ -61,7 +61,7 @@ public ScheduledJobDetailDto mapToDetailDto(ScheduledJobHistory latestHistory) { dto.setEnabled(this.enabled); dto.setSystem(this.system); dto.setOneTime(this.oneTime); - if(latestHistory != null) dto.setLastExecutionStatus(latestHistory.getSchedulerExecutionStatus()); + if (latestHistory != null) dto.setLastExecutionStatus(latestHistory.getSchedulerExecutionStatus()); return dto; } @@ -75,7 +75,7 @@ public ScheduledJobDto mapToDto(ScheduledJobHistory latestHistory) { dto.setEnabled(this.enabled); dto.setOneTime(this.oneTime); dto.setSystem(this.system); - if(latestHistory != null) dto.setLastExecutionStatus(latestHistory.getSchedulerExecutionStatus()); + if (latestHistory != null) dto.setLastExecutionStatus(latestHistory.getSchedulerExecutionStatus()); return dto; } diff --git a/src/main/java/com/czertainly/core/dao/repository/ScheduledJobsRepository.java b/src/main/java/com/czertainly/core/dao/repository/ScheduledJobsRepository.java index b239e27a1..00dccc6e8 100644 --- a/src/main/java/com/czertainly/core/dao/repository/ScheduledJobsRepository.java +++ b/src/main/java/com/czertainly/core/dao/repository/ScheduledJobsRepository.java @@ -3,9 +3,10 @@ import com.czertainly.core.dao.entity.ScheduledJob; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; @Repository public interface ScheduledJobsRepository extends SecurityFilterRepository { - ScheduledJob findByJobName(String jobName); + Optional findByJobName(String jobName); } diff --git a/src/main/java/com/czertainly/core/event/transaction/DiscoveryFinishedEvent.java b/src/main/java/com/czertainly/core/event/transaction/DiscoveryFinishedEvent.java index 17a91c10b..3956bea21 100644 --- a/src/main/java/com/czertainly/core/event/transaction/DiscoveryFinishedEvent.java +++ b/src/main/java/com/czertainly/core/event/transaction/DiscoveryFinishedEvent.java @@ -1,6 +1,8 @@ package com.czertainly.core.event.transaction; +import com.czertainly.core.tasks.ScheduledJobInfo; + import java.util.UUID; -public record DiscoveryFinishedEvent(UUID discoveryUuid, UUID loggedUserUuid) { +public record DiscoveryFinishedEvent(UUID discoveryUuid, UUID loggedUserUuid, ScheduledJobInfo scheduledJobInfo) { } diff --git a/src/main/java/com/czertainly/core/event/transaction/ScheduledJobFinishedEvent.java b/src/main/java/com/czertainly/core/event/transaction/ScheduledJobFinishedEvent.java new file mode 100644 index 000000000..bbc8db0e7 --- /dev/null +++ b/src/main/java/com/czertainly/core/event/transaction/ScheduledJobFinishedEvent.java @@ -0,0 +1,7 @@ +package com.czertainly.core.event.transaction; + +import com.czertainly.core.model.ScheduledTaskResult; +import com.czertainly.core.tasks.ScheduledJobInfo; + +public record ScheduledJobFinishedEvent(ScheduledJobInfo scheduledJobInfo, ScheduledTaskResult result) { +} diff --git a/src/main/java/com/czertainly/core/messaging/listeners/EventListener.java b/src/main/java/com/czertainly/core/messaging/listeners/EventListener.java index 6dae9ea8b..b2816f4dc 100644 --- a/src/main/java/com/czertainly/core/messaging/listeners/EventListener.java +++ b/src/main/java/com/czertainly/core/messaging/listeners/EventListener.java @@ -10,7 +10,9 @@ import com.czertainly.core.messaging.model.EventMessage; import com.czertainly.core.service.CertificateEventHistoryService; import com.czertainly.core.service.DiscoveryService; +import com.czertainly.core.tasks.ScheduledJobInfo; import com.czertainly.core.util.AuthHelper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.annotation.RabbitListener; @@ -31,13 +33,15 @@ public class EventListener { private CertificateEventHistoryService certificateEventHistoryService; private DiscoveryService discoveryService; private AuthHelper authHelper; + private final ObjectMapper mapper = new ObjectMapper(); @Autowired public void setAuthHelper(AuthHelper authHelper) { this.authHelper = authHelper; } + @Autowired - public void setDiscoveryService(DiscoveryService discoveryService){ + public void setDiscoveryService(DiscoveryService discoveryService) { this.discoveryService = discoveryService; } @@ -49,11 +53,13 @@ public void setCertificateEventHistoryService(CertificateEventHistoryService cer @RabbitListener(queues = RabbitMQConstants.QUEUE_EVENTS_NAME, messageConverter = "jsonMessageConverter", concurrency = "3") public void processMessage(EventMessage eventMessage) throws NotFoundException, CertificateException, NoSuchAlgorithmException, RuleException, AttributeException { switch (eventMessage.getResource()) { - case CERTIFICATE -> certificateEventHistoryService.addEventHistory(eventMessage.getResourceUUID(), CertificateEvent.findByCode(eventMessage.getEventName()), CertificateEventStatus.valueOf(eventMessage.getEventStatus()), eventMessage.getEventMessage(), eventMessage.getEventDetail()); - case DISCOVERY -> - { + case CERTIFICATE -> + certificateEventHistoryService.addEventHistory(eventMessage.getResourceUUID(), CertificateEvent.findByCode(eventMessage.getEventName()), CertificateEventStatus.valueOf(eventMessage.getEventStatus()), eventMessage.getEventMessage(), eventMessage.getEventDetail()); + case DISCOVERY -> { authHelper.authenticateAsUser(eventMessage.getUserUuid()); - if (Objects.equals(eventMessage.getEventName(), ResourceEvent.DISCOVERY_FINISHED.getCode())) discoveryService.evaluateDiscoveryTriggers(eventMessage.getResourceUUID(), eventMessage.getUserUuid()); + if (Objects.equals(eventMessage.getEventName(), ResourceEvent.DISCOVERY_FINISHED.getCode())) { + discoveryService.evaluateDiscoveryTriggers(eventMessage.getResourceUUID(), eventMessage.getUserUuid(), mapper.convertValue(eventMessage.getEventData(), ScheduledJobInfo.class)); + } } default -> logger.warn("Event handling is supported only for certificates for now"); } diff --git a/src/main/java/com/czertainly/core/messaging/listeners/SchedulerListener.java b/src/main/java/com/czertainly/core/messaging/listeners/SchedulerListener.java index 1da33ac52..652b63069 100644 --- a/src/main/java/com/czertainly/core/messaging/listeners/SchedulerListener.java +++ b/src/main/java/com/czertainly/core/messaging/listeners/SchedulerListener.java @@ -1,81 +1,37 @@ package com.czertainly.core.messaging.listeners; -import com.czertainly.api.exception.ConnectorException; +import com.czertainly.api.exception.NotFoundException; import com.czertainly.api.exception.SchedulerException; import com.czertainly.api.model.scheduler.SchedulerJobExecutionMessage; -import com.czertainly.api.model.scheduler.SchedulerJobExecutionStatus; -import com.czertainly.core.dao.entity.ScheduledJob; -import com.czertainly.core.dao.entity.ScheduledJobHistory; -import com.czertainly.core.dao.repository.ScheduledJobHistoryRepository; -import com.czertainly.core.dao.repository.ScheduledJobsRepository; import com.czertainly.core.messaging.configuration.RabbitMQConstants; -import com.czertainly.core.tasks.SchedulerJobProcessor; +import com.czertainly.core.service.SchedulerService; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; -import java.util.Date; - @Component @Transactional public class SchedulerListener { - private ApplicationContext applicationContext; - - private ScheduledJobHistoryRepository scheduledJobHistoryRepository; + private static final Logger logger = LoggerFactory.getLogger(SchedulerListener.class); - private ScheduledJobsRepository scheduledJobsRepository; + private SchedulerService schedulerService; - private static final Logger logger = LoggerFactory.getLogger(SchedulerListener.class); + @Autowired + public void setSchedulerService(SchedulerService schedulerService) { + this.schedulerService = schedulerService; + } @RabbitListener(queues = RabbitMQConstants.QUEUE_SCHEDULER_NAME, messageConverter = "jsonMessageConverter", concurrency = "10") public void processMessage(SchedulerJobExecutionMessage schedulerMessage) { - logger.info("Received scheduler message: {}", schedulerMessage); - + logger.debug("Received scheduler message: {}", schedulerMessage); try { - final Class clazz = Class.forName(schedulerMessage.getClassToBeExecuted()); - final Object clazzObject = applicationContext.getBean(clazz); - if (clazzObject != null && clazzObject instanceof SchedulerJobProcessor processor) { - logger.info("Job {} is executed.", schedulerMessage.getJobName()); - final SchedulerJobProcessor schedulerJobProcessor = processor; - schedulerJobProcessor.processTask(schedulerMessage.getJobName()); - logger.info("Job {} was processed.", schedulerMessage.getJobName()); - } - - } catch (SchedulerException | ConnectorException | ClassNotFoundException e) { - logger.error("Unable to process the job {}", schedulerMessage.getJobName()); - - final ScheduledJob scheduledJob = scheduledJobsRepository.findByJobName(schedulerMessage.getJobName()); - if (scheduledJob != null) { - final ScheduledJobHistory scheduledJobHistory = new ScheduledJobHistory(); - scheduledJobHistory.setScheduledJobUuid(scheduledJob.getUuid()); - scheduledJobHistory.setJobExecution(new Date()); - scheduledJobHistory.setSchedulerExecutionStatus(SchedulerJobExecutionStatus.FAILED); - scheduledJobHistoryRepository.save(scheduledJobHistory); - } else { - logger.error("There is no such job {} registered.", schedulerMessage.getJobName()); - } + schedulerService.runScheduledJob(schedulerMessage.getJobName()); + } catch (SchedulerException | NotFoundException e) { + logger.error("Unable to process the job {}. Error: {}", schedulerMessage.getJobName(), e.getMessage(), e); } } - - // SETTERs - - @Autowired - public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - @Autowired - public void setScheduledJobHistoryRepository(ScheduledJobHistoryRepository scheduledJobHistoryRepository) { - this.scheduledJobHistoryRepository = scheduledJobHistoryRepository; - } - - @Autowired - public void setScheduledJobsRepository(ScheduledJobsRepository scheduledJobsRepository) { - this.scheduledJobsRepository = scheduledJobsRepository; - } } diff --git a/src/main/java/com/czertainly/core/messaging/model/EventMessage.java b/src/main/java/com/czertainly/core/messaging/model/EventMessage.java index 4fcdf40c4..a24b59aed 100644 --- a/src/main/java/com/czertainly/core/messaging/model/EventMessage.java +++ b/src/main/java/com/czertainly/core/messaging/model/EventMessage.java @@ -1,7 +1,6 @@ package com.czertainly.core.messaging.model; import com.czertainly.api.model.core.auth.Resource; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; import java.util.UUID; @@ -26,4 +25,6 @@ public class EventMessage { private UUID userUuid; + private Object eventData; + } diff --git a/src/main/java/com/czertainly/core/messaging/producers/EventProducer.java b/src/main/java/com/czertainly/core/messaging/producers/EventProducer.java index 54386ad04..42ca68cdd 100644 --- a/src/main/java/com/czertainly/core/messaging/producers/EventProducer.java +++ b/src/main/java/com/czertainly/core/messaging/producers/EventProducer.java @@ -7,6 +7,7 @@ import com.czertainly.api.model.core.other.ResourceEvent; import com.czertainly.core.messaging.configuration.RabbitMQConstants; import com.czertainly.core.messaging.model.EventMessage; +import com.czertainly.core.tasks.ScheduledJobInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -32,20 +33,20 @@ public void produceMessage(final EventMessage eventMessage) { public void produceCertificateEventMessage(final UUID certificateUUID, final String eventName, final String eventStatus, final String message, final String detail) { logger.debug("Sending Certificate {} event message: {}", certificateUUID, message); - final EventMessage eventMessage = new EventMessage(Resource.CERTIFICATE, certificateUUID, eventName, eventStatus, message, detail, null); + final EventMessage eventMessage = new EventMessage(Resource.CERTIFICATE, certificateUUID, eventName, eventStatus, message, detail, null, null); produceMessage(eventMessage); } public void produceCertificateStatusChangeEventMessage(final UUID certificateUUID, final CertificateEvent certificateEvent, final CertificateEventStatus eventStatus, IPlatformEnum oldStatus, IPlatformEnum newStatus) { String message = String.format("Certificate %s changed from %s to %s.", certificateEvent == CertificateEvent.UPDATE_STATE ? "state" : "validation status", oldStatus.getLabel(), newStatus.getLabel()); logger.debug("Sending Certificate {} event message: {}", certificateUUID, message); - final EventMessage eventMessage = new EventMessage(Resource.CERTIFICATE, certificateUUID, certificateEvent.getCode(), eventStatus.toString(), message, null, null); + final EventMessage eventMessage = new EventMessage(Resource.CERTIFICATE, certificateUUID, certificateEvent.getCode(), eventStatus.toString(), message, null, null, null); produceMessage(eventMessage); } - public void produceDiscoveryFinishedEventMessage(final UUID discoveryUuid, final UUID userUuid, final ResourceEvent event) { + public void produceDiscoveryFinishedEventMessage(final UUID discoveryUuid, final UUID userUuid, final ResourceEvent event, final ScheduledJobInfo scheduledJobInfo) { - final EventMessage eventMessage = new EventMessage(Resource.DISCOVERY, discoveryUuid, event.getCode(), null, null, null, userUuid); + final EventMessage eventMessage = new EventMessage(Resource.DISCOVERY, discoveryUuid, event.getCode(), null, null, null, userUuid, scheduledJobInfo); produceMessage(eventMessage); } diff --git a/src/main/java/com/czertainly/core/service/DiscoveryService.java b/src/main/java/com/czertainly/core/service/DiscoveryService.java index f75100649..3638d2f40 100644 --- a/src/main/java/com/czertainly/core/service/DiscoveryService.java +++ b/src/main/java/com/czertainly/core/service/DiscoveryService.java @@ -9,6 +9,7 @@ import com.czertainly.api.model.core.search.SearchFieldDataByGroupDto; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; +import com.czertainly.core.tasks.ScheduledJobInfo; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -34,7 +35,7 @@ public interface DiscoveryService extends ResourceExtensionService { DiscoveryCertificateResponseDto getDiscoveryCertificates(SecuredUUID uuid, Boolean newlyDiscovered, int itemsPerPage, int pageNumber) throws NotFoundException; DiscoveryHistoryDetailDto createDiscovery(DiscoveryDto request, boolean saveEntity) throws AlreadyExistException, ConnectorException, AttributeException; - DiscoveryHistoryDetailDto runDiscovery(UUID discoveryUuid); + DiscoveryHistoryDetailDto runDiscovery(UUID discoveryUuid, ScheduledJobInfo scheduledJobInfo); void runDiscoveryAsync(UUID discoveryUuid); void deleteDiscovery(SecuredUUID uuid) throws NotFoundException; @@ -50,6 +51,6 @@ public interface DiscoveryService extends ResourceExtensionService { List getSearchableFieldInformationByGroup(); - void evaluateDiscoveryTriggers(UUID discoveryUuid, UUID userUuid) throws NotFoundException, RuleException, CertificateException, NoSuchAlgorithmException, AttributeException; + void evaluateDiscoveryTriggers(UUID discoveryUuid, UUID userUuid, ScheduledJobInfo scheduledJobInfo) throws NotFoundException, RuleException, CertificateException, NoSuchAlgorithmException, AttributeException; } \ No newline at end of file diff --git a/src/main/java/com/czertainly/core/service/SchedulerService.java b/src/main/java/com/czertainly/core/service/SchedulerService.java index ca1a85a81..691d54f76 100644 --- a/src/main/java/com/czertainly/core/service/SchedulerService.java +++ b/src/main/java/com/czertainly/core/service/SchedulerService.java @@ -8,6 +8,7 @@ import com.czertainly.api.model.core.scheduler.ScheduledJobDetailDto; import com.czertainly.api.model.scheduler.UpdateScheduledJob; import com.czertainly.core.security.authz.SecurityFilter; +import com.czertainly.core.tasks.ScheduledJobTask; public interface SchedulerService { @@ -25,4 +26,9 @@ public interface SchedulerService { ScheduledJobDetailDto updateScheduledJob(String uuid, UpdateScheduledJob request) throws NotFoundException, SchedulerException; + ScheduledJobDetailDto registerScheduledJob(Class scheduledJobTaskClass) throws SchedulerException; + + ScheduledJobDetailDto registerScheduledJob(Class scheduledJobTaskClass, String jobName, String cronExpression, boolean oneTime, Object taskData) throws SchedulerException; + + void runScheduledJob(String jobName) throws SchedulerException, NotFoundException; } diff --git a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java index c67d33fb3..8a232bfb1 100644 --- a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java @@ -24,6 +24,7 @@ import com.czertainly.api.model.core.search.FilterFieldSource; import com.czertainly.api.model.core.search.SearchFieldDataByGroupDto; import com.czertainly.api.model.core.search.SearchFieldDataDto; +import com.czertainly.api.model.scheduler.SchedulerJobExecutionStatus; import com.czertainly.core.aop.AuditLogged; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; @@ -37,15 +38,18 @@ import com.czertainly.core.event.transaction.CertificateValidationEvent; import com.czertainly.core.event.transaction.DiscoveryFinishedEvent; import com.czertainly.core.event.transaction.DiscoveryProgressEvent; +import com.czertainly.core.event.transaction.ScheduledJobFinishedEvent; import com.czertainly.core.messaging.model.NotificationRecipient; import com.czertainly.core.messaging.producers.EventProducer; import com.czertainly.core.messaging.producers.NotificationProducer; +import com.czertainly.core.model.ScheduledTaskResult; import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authz.ExternalAuthorization; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.*; import com.czertainly.core.service.handler.CertificateHandler; +import com.czertainly.core.tasks.ScheduledJobInfo; import com.czertainly.core.util.*; import com.pivovarit.collectors.ParallelCollectors; import jakarta.persistence.criteria.CriteriaBuilder; @@ -294,6 +298,7 @@ public Long statisticsDiscoveryCount(SecurityFilter filter) { @Override @AuditLogged(originator = ObjectType.FE, affected = ObjectType.DISCOVERY, operation = OperationType.CREATE) + @ExternalAuthorization(resource = Resource.DISCOVERY, action = ResourceAction.CREATE) public DiscoveryHistoryDetailDto createDiscovery(final DiscoveryDto request, final boolean saveEntity) throws AlreadyExistException, ConnectorException, AttributeException { if (discoveryRepository.findByName(request.getName()).isPresent()) { throw new AlreadyExistException(DiscoveryHistory.class, request.getName()); @@ -306,7 +311,6 @@ public DiscoveryHistoryDetailDto createDiscovery(final DiscoveryDto request, fin attributeEngine.validateCustomAttributesContent(Resource.DISCOVERY, request.getCustomAttributes()); connectorService.mergeAndValidateAttributes(SecuredUUID.fromUUID(connector.getUuid()), FunctionGroupCode.DISCOVERY_PROVIDER, request.getAttributes(), request.getKind()); - DiscoveryHistory discovery = new DiscoveryHistory(); discovery.setName(request.getName()); discovery.setConnectorName(connector.getName()); @@ -349,194 +353,36 @@ public DiscoveryHistoryDetailDto createDiscovery(final DiscoveryDto request, fin @AuditLogged(originator = ObjectType.FE, affected = ObjectType.DISCOVERY, operation = OperationType.CREATE) @ExternalAuthorization(resource = Resource.DISCOVERY, action = ResourceAction.CREATE) public void runDiscoveryAsync(UUID discoveryUuid) { - runDiscovery(discoveryUuid); + runDiscovery(discoveryUuid, null); } @Override @ExternalAuthorization(resource = Resource.DISCOVERY, action = ResourceAction.CREATE) - public DiscoveryHistoryDetailDto runDiscovery(UUID discoveryUuid) { + public DiscoveryHistoryDetailDto runDiscovery(UUID discoveryUuid, ScheduledJobInfo scheduledJobInfo) { UUID loggedUserUuid = UUID.fromString(AuthHelper.getUserIdentification().getUuid()); // reload discovery modal with all association since it could be in separate transaction/session due to async DiscoveryHistory discovery = discoveryRepository.findWithTriggersByUuid(discoveryUuid); - logger.info("Starting discovery: name={}, uuid={}", discovery.getName(), discovery.getUuid()); try { - DiscoveryRequestDto dtoRequest = new DiscoveryRequestDto(); - dtoRequest.setName(discovery.getName()); - dtoRequest.setKind(discovery.getKind()); - - Connector connector = connectorService.getConnectorEntity( - SecuredUUID.fromString(discovery.getConnectorUuid().toString())); - - // Load complete credential data - var dataAttributes = attributeEngine.getDefinitionObjectAttributeContent( - AttributeType.DATA, connector.getUuid(), null, Resource.DISCOVERY, discovery.getUuid()); - credentialService.loadFullCredentialData(dataAttributes); - dtoRequest.setAttributes(AttributeDefinitionUtils.getClientAttributes(dataAttributes)); - - DiscoveryProviderDto response = discoveryApiClient.discoverCertificates(connector.mapToDto(), dtoRequest); - - logger.debug("Discovery response: name={}, uuid={}, status={}, total={}", - discovery.getName(), discovery.getUuid(), response.getStatus(), response.getTotalCertificatesDiscovered()); - - discovery.setDiscoveryConnectorReference(response.getUuid()); - discoveryRepository.save(discovery); - - DiscoveryDataRequestDto getRequest = new DiscoveryDataRequestDto(); - getRequest.setName(response.getName()); - getRequest.setKind(discovery.getKind()); - getRequest.setPageNumber(1); - getRequest.setItemsPerPage(MAXIMUM_CERTIFICATES_PER_PAGE); - - boolean waitForCompletion = checkForCompletion(response); - boolean isReachedMaxTime = false; - int oldCertificateCount = 0; - while (waitForCompletion) { - if (discovery.getDiscoveryConnectorReference() == null) { - discovery.setStatus(DiscoveryStatus.FAILED); - discovery.setMessage("Discovery does not have associated connector"); - discoveryRepository.save(discovery); - return discovery.mapToDto(); - } - logger.debug("Waiting {}ms for discovery to be completed: name={}, uuid={}", - SLEEP_TIME, discovery.getName(), discovery.getUuid()); - Thread.sleep(SLEEP_TIME); - - response = discoveryApiClient.getDiscoveryData(connector.mapToDto(), getRequest, response.getUuid()); - - logger.debug("Discovery response: name={}, uuid={}, status={}, total={}", - discovery.getName(), discovery.getUuid(), response.getStatus(), response.getTotalCertificatesDiscovered()); - - if ((discovery.getStartTime().getTime() - new Date().getTime()) / 1000 > MAXIMUM_WAIT_TIME - && !isReachedMaxTime && oldCertificateCount == response.getTotalCertificatesDiscovered()) { - isReachedMaxTime = true; - discovery.setStatus(DiscoveryStatus.WARNING); - discovery.setMessage( - "Discovery " + discovery.getName() + " exceeded maximum time of " - + MAXIMUM_WAIT_TIME / (60 * 60) + " hours. There are no changes in number " + - "of certificates discovered. Please abort the discovery if the provider " + - "is stuck in state " + DiscoveryStatus.IN_PROGRESS.getLabel()); - discoveryRepository.save(discovery); - } - - oldCertificateCount = response.getTotalCertificatesDiscovered(); - waitForCompletion = checkForCompletion(response); - } - - - int currentPage = 1; - int currentTotal = 0; - Set uniqueCertificateContents = new HashSet<>(); - List duplicateCertificates = new ArrayList<>(); - - List> futures = new ArrayList<>(); - discovery.setTotalCertificatesDiscovered(response.getTotalCertificatesDiscovered()); - discovery.setConnectorTotalCertificatesDiscovered(response.getTotalCertificatesDiscovered()); - discovery.setConnectorStatus(response.getStatus()); - discoveryRepository.save(discovery); + Connector connector = connectorService.getConnectorEntity(SecuredUUID.fromString(discovery.getConnectorUuid().toString())); - if (response.getTotalCertificatesDiscovered() == 0 && response.getStatus() == DiscoveryStatus.FAILED) { - discovery.setStatus(DiscoveryStatus.FAILED); - discovery.setMessage("Discovery has failed on connector side without any certificates found."); - notificationProducer.produceNotificationText(Resource.DISCOVERY, discovery.getUuid(), NotificationRecipient.buildUserNotificationRecipient(loggedUserUuid), String.format("Discovery %s has finished with status %s", discovery.getName(), discovery.getStatus()), discovery.getMessage()); + // discover certificates by provider + DiscoveryProviderDto providerResponse = discoverCertificatesByProvider(discovery, connector, loggedUserUuid); + if (providerResponse == null) { return discovery.mapToDto(); } - Set certMetadataUuids = new HashSet<>(); - try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { - while (currentTotal < response.getTotalCertificatesDiscovered()) { - getRequest.setPageNumber(currentPage); - getRequest.setItemsPerPage(MAXIMUM_CERTIFICATES_PER_PAGE); - response = discoveryApiClient.getDiscoveryData(connector.mapToDto(), getRequest, response.getUuid()); - - if (response.getCertificateData().isEmpty()) { - discovery.setMessage(String.format("Retrieved only %d certificates but provider discovered %d " + - "certificates in total.", currentTotal, response.getTotalCertificatesDiscovered())); - break; - } - if (response.getCertificateData().size() > MAXIMUM_CERTIFICATES_PER_PAGE) { - updateDiscovery(discovery, response, DiscoveryStatus.FAILED); - logger.error("Too many content in response. Maximum processable is {}.", MAXIMUM_CERTIFICATES_PER_PAGE); - throw new InterruptedException( - "Too many content in response to process. Maximum processable is " + MAXIMUM_CERTIFICATES_PER_PAGE); - } - // categorize certs and collect metadata definitions - List metadataDefinitions = new ArrayList<>(); - List discoveredCertificates = new ArrayList<>(); - response.getCertificateData().forEach(c -> { - if (uniqueCertificateContents.contains(c.getBase64Content())) { - duplicateCertificates.add(c); - } else { - discoveredCertificates.add(c); - uniqueCertificateContents.add(c.getBase64Content()); - } - - for (MetadataAttribute m : c.getMeta()) { - if (!certMetadataUuids.contains(m.getUuid())) { - metadataDefinitions.add(m); - certMetadataUuids.add(m.getUuid()); - } - } - }); - - // add/update certificate metadata to prevent creating duplicate definitions in parallel processing - certificateHandler.updateMetadataDefinition(metadataDefinitions, connector.getUuid(), connector.getName()); - - // run in separate virtual thread and continue - final int finalCurrentPage = currentPage; - futures.add(executor.submit(() -> { - try { - logger.trace("Waiting to download batch {} of discovered certificates for discovery {}.", finalCurrentPage, discovery.getName()); - downloadCertSemaphore.acquire(); - logger.trace("Downloading batch {} of discovered certificates for discovery {}.", finalCurrentPage, discovery.getName()); - - certificateHandler.createDiscoveredCertificate(String.valueOf(finalCurrentPage), discovery, discoveredCertificates); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.error("Downloading batch {} of discovered certificates for discovery {} interrupted.", finalCurrentPage, discovery.getName()); - } catch (Exception e) { - logger.error("Downloading batch {} of discovered certificates for discovery {} failed.", finalCurrentPage, discovery.getName()); - } finally { - logger.trace("Downloading batch {} of discovered certificates for discovery {} finalized. Released semaphore.", finalCurrentPage, discovery.getName()); - downloadCertSemaphore.release(); - } - })); - - ++currentPage; - currentTotal += response.getCertificateData().size(); - - if (futures.size() >= MAXIMUM_PARALLELISM) { - logger.debug("Waiting for {} download tasks for discovery {}", futures.size(), discovery.getName()); - for (Future future : futures) { - future.get(); - } - logger.debug("{} download tasks for discovery {} finished", futures.size(), discovery.getName()); - futures.clear(); - } - } - - // Wait for all tasks to complete - logger.debug("Waiting for {} download tasks for discovery {}", futures.size(), discovery.getName()); - for (Future future : futures) { - future.get(); - } - logger.debug("{} download tasks for discovery {} finished", futures.size(), discovery.getName()); - } catch (Exception e) { - logger.error("An error occurred during downloading discovered certificate of discovery {}: {}", discovery.getName(), e.getMessage(), e); - if (e instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } - } + // download and create discovered certificates + List duplicateCertificates = downloadDiscoveredCertificates(discovery, connector, providerResponse); - if (discoveryCertificateRepository.countByDiscovery(discovery) == 0 && response.getStatus() == DiscoveryStatus.FAILED) { + if (discoveryCertificateRepository.countByDiscovery(discovery) == 0 && providerResponse.getStatus() == DiscoveryStatus.FAILED) { discovery.setStatus(DiscoveryStatus.FAILED); discovery.setMessage("Discovery has failed on connector side with some certificates found, but none of them has been downloaded."); notificationProducer.produceNotificationText(Resource.DISCOVERY, discovery.getUuid(), NotificationRecipient.buildUserNotificationRecipient(loggedUserUuid), String.format("Discovery %s has finished with status %s", discovery.getName(), discovery.getStatus()), discovery.getMessage()); return discovery.mapToDto(); } - // process duplicates for (DiscoveryProviderCertificateDataDto certificate : duplicateCertificates) { try { @@ -554,11 +400,11 @@ public DiscoveryHistoryDetailDto runDiscovery(UUID discoveryUuid) { } } - updateDiscovery(discovery, response, DiscoveryStatus.PROCESSING); - logger.debug("Going to process {} certificates", response.getTotalCertificatesDiscovered()); + updateDiscovery(discovery, providerResponse, DiscoveryStatus.PROCESSING); + logger.debug("Going to process {} certificates", providerResponse.getTotalCertificatesDiscovered()); // Publish the custom event after transaction completion - applicationEventPublisher.publishEvent(new DiscoveryFinishedEvent(discovery.getUuid(), loggedUserUuid)); + applicationEventPublisher.publishEvent(new DiscoveryFinishedEvent(discovery.getUuid(), loggedUserUuid, scheduledJobInfo)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); discovery.setStatus(DiscoveryStatus.FAILED); @@ -579,6 +425,187 @@ public DiscoveryHistoryDetailDto runDiscovery(UUID discoveryUuid) { return discovery.mapToDto(); } + private DiscoveryProviderDto discoverCertificatesByProvider(final DiscoveryHistory discovery, final Connector connector, final UUID loggedUserUuid) throws ConnectorException, InterruptedException { + DiscoveryRequestDto dtoRequest = new DiscoveryRequestDto(); + dtoRequest.setName(discovery.getName()); + dtoRequest.setKind(discovery.getKind()); + + // Load complete credential data + var dataAttributes = attributeEngine.getDefinitionObjectAttributeContent( + AttributeType.DATA, connector.getUuid(), null, Resource.DISCOVERY, discovery.getUuid()); + credentialService.loadFullCredentialData(dataAttributes); + dtoRequest.setAttributes(AttributeDefinitionUtils.getClientAttributes(dataAttributes)); + + DiscoveryProviderDto response = discoveryApiClient.discoverCertificates(connector.mapToDto(), dtoRequest); + + logger.debug("Discovery response: name={}, uuid={}, status={}, total={}", + discovery.getName(), discovery.getUuid(), response.getStatus(), response.getTotalCertificatesDiscovered()); + + discovery.setDiscoveryConnectorReference(response.getUuid()); + discoveryRepository.save(discovery); + + DiscoveryDataRequestDto getRequest = new DiscoveryDataRequestDto(); + getRequest.setName(response.getName()); + getRequest.setKind(discovery.getKind()); + getRequest.setPageNumber(1); + getRequest.setItemsPerPage(MAXIMUM_CERTIFICATES_PER_PAGE); + + boolean waitForCompletion = checkForCompletion(response); + boolean isReachedMaxTime = false; + int oldCertificateCount = 0; + while (waitForCompletion) { + if (discovery.getDiscoveryConnectorReference() == null) { + discovery.setStatus(DiscoveryStatus.FAILED); + discovery.setMessage("Discovery does not have associated connector"); + discoveryRepository.save(discovery); + return null; + } + logger.debug("Waiting {}ms for discovery to be completed: name={}, uuid={}", + SLEEP_TIME, discovery.getName(), discovery.getUuid()); + Thread.sleep(SLEEP_TIME); + + response = discoveryApiClient.getDiscoveryData(connector.mapToDto(), getRequest, response.getUuid()); + + logger.debug("Discovery response: name={}, uuid={}, status={}, total={}", + discovery.getName(), discovery.getUuid(), response.getStatus(), response.getTotalCertificatesDiscovered()); + + if ((discovery.getStartTime().getTime() - new Date().getTime()) / 1000 > MAXIMUM_WAIT_TIME + && !isReachedMaxTime && oldCertificateCount == response.getTotalCertificatesDiscovered()) { + isReachedMaxTime = true; + discovery.setStatus(DiscoveryStatus.WARNING); + discovery.setMessage( + "Discovery " + discovery.getName() + " exceeded maximum time of " + + MAXIMUM_WAIT_TIME / (60 * 60) + " hours. There are no changes in number " + + "of certificates discovered. Please abort the discovery if the provider " + + "is stuck in state " + DiscoveryStatus.IN_PROGRESS.getLabel()); + discoveryRepository.save(discovery); + } + + oldCertificateCount = response.getTotalCertificatesDiscovered(); + waitForCompletion = checkForCompletion(response); + } + + discovery.setTotalCertificatesDiscovered(response.getTotalCertificatesDiscovered()); + discovery.setConnectorTotalCertificatesDiscovered(response.getTotalCertificatesDiscovered()); + discovery.setConnectorStatus(response.getStatus()); + if (response.getTotalCertificatesDiscovered() == 0 && response.getStatus() == DiscoveryStatus.FAILED) { + discovery.setStatus(DiscoveryStatus.FAILED); + discovery.setMessage("Discovery has failed on connector side without any certificates found."); + notificationProducer.produceNotificationText(Resource.DISCOVERY, discovery.getUuid(), NotificationRecipient.buildUserNotificationRecipient(loggedUserUuid), String.format("Discovery %s has finished with status %s", discovery.getName(), discovery.getStatus()), discovery.getMessage()); + discoveryRepository.save(discovery); + return null; + } + + discoveryRepository.save(discovery); + return response; + } + + private List downloadDiscoveredCertificates(final DiscoveryHistory discovery, final Connector connector, DiscoveryProviderDto response) { + int currentPage = 1; + int currentTotal = 0; + + DiscoveryDataRequestDto getRequest = new DiscoveryDataRequestDto(); + getRequest.setName(response.getName()); + getRequest.setKind(discovery.getKind()); + getRequest.setPageNumber(1); + getRequest.setItemsPerPage(MAXIMUM_CERTIFICATES_PER_PAGE); + + List> futures = new ArrayList<>(); + Set uniqueCertificateContents = new HashSet<>(); + List duplicateCertificates = new ArrayList<>(); + try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { + while (currentTotal < response.getTotalCertificatesDiscovered()) { + getRequest.setPageNumber(currentPage); + getRequest.setItemsPerPage(MAXIMUM_CERTIFICATES_PER_PAGE); + response = discoveryApiClient.getDiscoveryData(connector.mapToDto(), getRequest, response.getUuid()); + + if (response.getCertificateData().isEmpty()) { + discovery.setMessage(String.format("Retrieved only %d certificates but provider discovered %d " + + "certificates in total.", currentTotal, response.getTotalCertificatesDiscovered())); + break; + } + if (response.getCertificateData().size() > MAXIMUM_CERTIFICATES_PER_PAGE) { + updateDiscovery(discovery, response, DiscoveryStatus.FAILED); + logger.error("Too many content in response. Maximum processable is {}.", MAXIMUM_CERTIFICATES_PER_PAGE); + throw new InterruptedException( + "Too many content in response to process. Maximum processable is " + MAXIMUM_CERTIFICATES_PER_PAGE); + } + + futures.add(downloadDiscoveredCertificatesBatchAsync(discovery, response, connector, uniqueCertificateContents, duplicateCertificates, executor, currentPage)); + + ++currentPage; + currentTotal += response.getCertificateData().size(); + + if (futures.size() >= MAXIMUM_PARALLELISM) { + logger.debug("Waiting for {} download tasks for discovery {}", futures.size(), discovery.getName()); + for (Future future : futures) { + future.get(); + } + logger.debug("{} download tasks for discovery {} finished", futures.size(), discovery.getName()); + futures.clear(); + } + } + + // Wait for all tasks to complete + logger.debug("Waiting for {} download tasks for discovery {}", futures.size(), discovery.getName()); + for (Future future : futures) { + future.get(); + } + logger.debug("{} download tasks for discovery {} finished", futures.size(), discovery.getName()); + } catch (Exception e) { + logger.error("An error occurred during downloading discovered certificate of discovery {}: {}", discovery.getName(), e.getMessage(), e); + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + } + + return duplicateCertificates; + } + + private Future downloadDiscoveredCertificatesBatchAsync(final DiscoveryHistory discovery, final DiscoveryProviderDto response, final Connector connector, final Set uniqueCertificateContents, final List duplicateCertificates, final ExecutorService executor, final int currentPage) { + // categorize certs and collect metadata definitions + Set certMetadataUuids = new HashSet<>(); + List metadataDefinitions = new ArrayList<>(); + List discoveredCertificates = new ArrayList<>(); + response.getCertificateData().forEach(c -> { + if (uniqueCertificateContents.contains(c.getBase64Content())) { + duplicateCertificates.add(c); + } else { + discoveredCertificates.add(c); + uniqueCertificateContents.add(c.getBase64Content()); + } + + for (MetadataAttribute m : c.getMeta()) { + if (!certMetadataUuids.contains(m.getUuid())) { + metadataDefinitions.add(m); + certMetadataUuids.add(m.getUuid()); + } + } + }); + + // add/update certificate metadata to prevent creating duplicate definitions in parallel processing + certificateHandler.updateMetadataDefinition(metadataDefinitions, connector.getUuid(), connector.getName()); + + // run in separate virtual thread and continue + return executor.submit(() -> { + try { + logger.trace("Waiting to download batch {} of discovered certificates for discovery {}.", currentPage, discovery.getName()); + downloadCertSemaphore.acquire(); + logger.trace("Downloading batch {} of discovered certificates for discovery {}.", currentPage, discovery.getName()); + + certificateHandler.createDiscoveredCertificate(String.valueOf(currentPage), discovery, discoveredCertificates); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.error("Downloading batch {} of discovered certificates for discovery {} interrupted.", currentPage, discovery.getName()); + } catch (Exception e) { + logger.error("Downloading batch {} of discovered certificates for discovery {} failed.", currentPage, discovery.getName()); + } finally { + logger.trace("Downloading batch {} of discovered certificates for discovery {} finalized. Released semaphore.", currentPage, discovery.getName()); + downloadCertSemaphore.release(); + } + }); + } + private void updateDiscovery(DiscoveryHistory modal, DiscoveryProviderDto response, DiscoveryStatus status) throws AttributeException { modal.setStatus(status); modal.setTotalCertificatesDiscovered(status == DiscoveryStatus.FAILED ? response.getTotalCertificatesDiscovered() : (int) discoveryCertificateRepository.countByDiscovery(modal)); @@ -591,7 +618,8 @@ public void handleDiscoveryFinishedEvent(DiscoveryFinishedEvent event) { eventProducer.produceDiscoveryFinishedEventMessage( event.discoveryUuid(), event.loggedUserUuid(), - ResourceEvent.DISCOVERY_FINISHED + ResourceEvent.DISCOVERY_FINISHED, + event.scheduledJobInfo() ); } @@ -635,7 +663,7 @@ public List getSearchableFieldInformationByGroup() { } @Override - public void evaluateDiscoveryTriggers(UUID discoveryUuid, UUID userUuid) { + public void evaluateDiscoveryTriggers(UUID discoveryUuid, UUID userUuid, ScheduledJobInfo scheduledJobInfo) { // Get newly discovered certificates DiscoveryHistory discovery = discoveryRepository.findWithTriggersByUuid(discoveryUuid); List discoveredCertificates = discoveryCertificateRepository.findByDiscoveryAndNewlyDiscovered(discovery, true, Pageable.unpaged()); @@ -685,6 +713,9 @@ public void evaluateDiscoveryTriggers(UUID discoveryUuid, UUID userUuid) { discoveryRepository.save(discovery); notificationProducer.produceNotificationText(Resource.DISCOVERY, discovery.getUuid(), NotificationRecipient.buildUserNotificationRecipient(userUuid), String.format("Discovery %s has finished with status %s", discovery.getName(), discovery.getStatus()), discovery.getMessage()); + if (scheduledJobInfo != null) { + applicationEventPublisher.publishEvent(new ScheduledJobFinishedEvent(scheduledJobInfo, new ScheduledTaskResult(discovery.getStatus() == DiscoveryStatus.FAILED ? SchedulerJobExecutionStatus.FAILED : SchedulerJobExecutionStatus.SUCCESS, discovery.getMessage(), Resource.DISCOVERY, discovery.getUuid().toString()))); + } applicationEventPublisher.publishEvent(new CertificateValidationEvent(null, discoveryUuid, discovery.getName(), null, null)); } diff --git a/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java index c9a799f41..5842b9e10 100644 --- a/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/SchedulerServiceImpl.java @@ -6,23 +6,27 @@ import com.czertainly.api.exception.ValidationError; import com.czertainly.api.exception.ValidationException; import com.czertainly.api.model.core.auth.Resource; -import com.czertainly.api.model.core.scheduler.PaginationRequestDto; -import com.czertainly.api.model.core.scheduler.ScheduledJobDetailDto; -import com.czertainly.api.model.core.scheduler.ScheduledJobHistoryResponseDto; -import com.czertainly.api.model.core.scheduler.ScheduledJobsResponseDto; +import com.czertainly.api.model.core.scheduler.*; import com.czertainly.api.model.scheduler.SchedulerJobDto; +import com.czertainly.api.model.scheduler.SchedulerJobExecutionStatus; import com.czertainly.api.model.scheduler.SchedulerRequestDto; import com.czertainly.api.model.scheduler.UpdateScheduledJob; -import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.ScheduledJob; import com.czertainly.core.dao.entity.ScheduledJobHistory; import com.czertainly.core.dao.repository.ScheduledJobHistoryRepository; import com.czertainly.core.dao.repository.ScheduledJobsRepository; +import com.czertainly.core.event.transaction.ScheduledJobFinishedEvent; +import com.czertainly.core.messaging.model.NotificationRecipient; +import com.czertainly.core.messaging.producers.NotificationProducer; +import com.czertainly.core.model.ScheduledTaskResult; import com.czertainly.core.model.auth.ResourceAction; import com.czertainly.core.security.authz.ExternalAuthorization; import com.czertainly.core.security.authz.SecuredUUID; import com.czertainly.core.security.authz.SecurityFilter; import com.czertainly.core.service.SchedulerService; +import com.czertainly.core.tasks.ScheduledJobInfo; +import com.czertainly.core.tasks.ScheduledJobTask; +import com.czertainly.core.util.AuthHelper; import com.czertainly.core.util.FilterPredicatesBuilder; import com.czertainly.core.util.RequestValidatorHelper; import jakarta.persistence.criteria.CriteriaBuilder; @@ -33,10 +37,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -47,11 +58,47 @@ public class SchedulerServiceImpl implements SchedulerService { private static final Logger logger = LoggerFactory.getLogger(SchedulerServiceImpl.class); + private AuthHelper authHelper; + + private ApplicationContext applicationContext; + + private NotificationProducer notificationProducer; + + private SchedulerApiClient schedulerApiClient; + private ScheduledJobsRepository scheduledJobsRepository; private ScheduledJobHistoryRepository scheduledJobHistoryRepository; - private SchedulerApiClient schedulerApiClient; + @Autowired + public void setAuthHelper(AuthHelper authHelper) { + this.authHelper = authHelper; + } + + @Autowired + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Autowired + public void setNotificationProducer(NotificationProducer notificationProducer) { + this.notificationProducer = notificationProducer; + } + + @Autowired + public void setScheduledJobsRepository(ScheduledJobsRepository scheduledJobsRepository) { + this.scheduledJobsRepository = scheduledJobsRepository; + } + + @Autowired + public void setSchedulerApiClient(SchedulerApiClient schedulerApiClient) { + this.schedulerApiClient = schedulerApiClient; + } + + @Autowired + public void setScheduledJobHistoryRepository(ScheduledJobHistoryRepository scheduledJobHistoryRepository) { + this.scheduledJobHistoryRepository = scheduledJobHistoryRepository; + } @Override @ExternalAuthorization(resource = Resource.SCHEDULED_JOB, action = ResourceAction.LIST) @@ -169,20 +216,136 @@ private void changeScheduledJobState(final String uuid, final boolean enabled) t } } - // SETTERs + // Scheduled job processing - @Autowired - public void setScheduledJobsRepository(ScheduledJobsRepository scheduledJobsRepository) { - this.scheduledJobsRepository = scheduledJobsRepository; + @Override + public ScheduledJobDetailDto registerScheduledJob(final Class scheduledJobTaskClass) throws SchedulerException { + final ScheduledJobTask scheduledJobTask = applicationContext.getBean(scheduledJobTaskClass); + return registerScheduler(scheduledJobTask, scheduledJobTask.getDefaultJobName(), scheduledJobTask.getDefaultCronExpression(), scheduledJobTask.isDefaultOneTimeJob(), null); } - @Autowired - public void setSchedulerApiClient(SchedulerApiClient schedulerApiClient) { - this.schedulerApiClient = schedulerApiClient; + @Override + public ScheduledJobDetailDto registerScheduledJob(final Class scheduledJobTaskClass, final String jobName, final String cronExpression, final boolean oneTime, final Object taskData) throws SchedulerException { + final ScheduledJobTask scheduledJobTask = applicationContext.getBean(scheduledJobTaskClass); + return registerScheduler(scheduledJobTask, jobName, cronExpression, oneTime, taskData); } - @Autowired - public void setScheduledJobHistoryRepository(ScheduledJobHistoryRepository scheduledJobHistoryRepository) { - this.scheduledJobHistoryRepository = scheduledJobHistoryRepository; + @Override + public void runScheduledJob(final String jobName) throws SchedulerException, NotFoundException { + final ScheduledJob scheduledJob = scheduledJobsRepository.findByJobName(jobName).orElseThrow(() -> new NotFoundException(ScheduledJobHistory.class, jobName)); + + ScheduledJobTask scheduledJobTask; + try { + final Class clazz = Class.forName(scheduledJob.getJobClassName()); + final Object clazzObject = applicationContext.getBean(clazz); + if (clazzObject instanceof ScheduledJobTask task) { + scheduledJobTask = task; + } else { + scheduledJobTask = null; + } + } catch (ClassNotFoundException ignored) { + scheduledJobTask = null; + } + + if (scheduledJobTask == null) { + String errorMessage = "Unknown scheduled task '%s' for job '%s'".formatted(scheduledJob.getJobClassName(), scheduledJob.getJobName()); + registerJobHistory(scheduledJob, SchedulerJobExecutionStatus.FAILED, errorMessage); + logger.error(errorMessage); + return; + } + + final ScheduledJobHistory scheduledJobHistory = registerJobHistory(scheduledJob, SchedulerJobExecutionStatus.STARTED, null); + + if (scheduledJob.getUserUuid() != null) { + authHelper.authenticateAsUser(scheduledJob.getUserUuid()); + } + final ScheduledTaskResult result = scheduledJobTask.performJob(new ScheduledJobInfo(scheduledJob.getJobName(), scheduledJob.getUuid(), scheduledJobHistory.getUuid()), scheduledJob.getObjectData()); + + if (result != null) { + updateJobHistory(scheduledJobHistory, result); + checkOneTimeJob(scheduledJob, result.getStatus()); + } + logger.info("Job {} was processed.", scheduledJob.getJobName()); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT) + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleScheduledJobFinishedEvent(ScheduledJobFinishedEvent event) throws NotFoundException, SchedulerException { + logger.debug("ScheduledJobFinished event handler: {}", event.scheduledJobInfo().jobUuid()); + final ScheduledJob scheduledJob = scheduledJobsRepository.findByUuid(SecuredUUID.fromUUID(event.scheduledJobInfo().jobUuid())).orElseThrow(() -> new NotFoundException(ScheduledJob.class, event.scheduledJobInfo().jobUuid())); + final ScheduledJobHistory scheduledJobHistory = scheduledJobHistoryRepository.findByUuid(SecuredUUID.fromUUID(event.scheduledJobInfo().jobHistoryUuid())).orElseThrow(() -> new NotFoundException(ScheduledJobHistory.class, event.scheduledJobInfo().jobHistoryUuid())); + updateJobHistory(scheduledJobHistory, event.result()); + checkOneTimeJob(scheduledJob, event.result().getStatus()); + } + + private ScheduledJobDetailDto registerScheduler(ScheduledJobTask scheduledJobTask, final String jobName, final String cronExpression, final boolean oneTime, final Object taskData) throws SchedulerException { + if (scheduledJobTask == null) { + throw new SchedulerException("Unknown scheduled task for job: " + jobName); + } + + final SchedulerJobDto schedulerDetail = new SchedulerJobDto(jobName, cronExpression, scheduledJobTask.getJobClassName()); + schedulerApiClient.schedulerCreate(new SchedulerRequestDto(schedulerDetail)); + + Optional scheduledJob = scheduledJobsRepository.findByJobName(jobName); + if (scheduledJob.isPresent()) { + logger.info("Job {} was already registered.", jobName); + return scheduledJob.get().mapToDetailDto(null); + } + + ScheduledJob scheduledJobEntity = new ScheduledJob(); + scheduledJobEntity.setJobName(jobName); + scheduledJobEntity.setCronExpression(cronExpression); + scheduledJobEntity.setObjectData(taskData); + scheduledJobEntity.setOneTime(oneTime); + scheduledJobEntity.setEnabled(true); + scheduledJobEntity.setSystem(scheduledJobTask.isSystemJob()); + scheduledJobEntity.setJobClassName(scheduledJobTask.getJobClassName()); + + try { + scheduledJobEntity.setUserUuid(UUID.fromString(AuthHelper.getUserIdentification().getUuid())); + } catch (ValidationException ignored) { + scheduledJobEntity.setUserUuid(null); + } + + scheduledJobsRepository.save(scheduledJobEntity); + + logger.info("Scheduler job {} was registered.", jobName); + + return scheduledJobEntity.mapToDetailDto(null); } + + private ScheduledJobHistory registerJobHistory(final ScheduledJob scheduledJob, SchedulerJobExecutionStatus status, String message) { + final ScheduledJobHistory scheduledJobHistory = new ScheduledJobHistory(); + scheduledJobHistory.setScheduledJobUuid(scheduledJob.getUuid()); + scheduledJobHistory.setJobExecution(new Date()); + scheduledJobHistory.setSchedulerExecutionStatus(status); + scheduledJobHistory.setResultMessage(message); + return scheduledJobHistoryRepository.save(scheduledJobHistory); + } + + private void updateJobHistory(final ScheduledJobHistory scheduledJobHistory, final ScheduledTaskResult result) { + scheduledJobHistory.setJobEndTime(new Date()); + scheduledJobHistory.setSchedulerExecutionStatus(result.getStatus()); + scheduledJobHistory.setResultMessage(result.getResultMessage()); + scheduledJobHistory.setResultObjectType(result.getResultObjectType()); + scheduledJobHistory.setResultObjectIdentification(result.getResultObjectIdentification()); + scheduledJobHistoryRepository.save(scheduledJobHistory); + } + + private void checkOneTimeJob(final ScheduledJob scheduledJob, final SchedulerJobExecutionStatus status) throws SchedulerException { + if (!scheduledJob.isSystem()) { + notificationProducer.produceNotificationScheduledJobCompleted(Resource.SCHEDULED_JOB, + scheduledJob.getUuid(), + NotificationRecipient.buildUserNotificationRecipient(scheduledJob.getUserUuid()), + scheduledJob.getJobName(), + scheduledJob.getJobType(), + status.getLabel()); + } + + if (SchedulerJobExecutionStatus.SUCCESS.equals(status) && scheduledJob.isOneTime()) { + schedulerApiClient.deleteScheduledJob(scheduledJob.getJobName()); + logger.info("Scheduled job {} was deleted/unscheduled because it was one time job only.", scheduledJob.getJobName()); + } + } + } diff --git a/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java b/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java index 427a00f9f..ffbd700b7 100644 --- a/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java +++ b/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java @@ -1,27 +1,23 @@ package com.czertainly.core.tasks; -import com.czertainly.api.exception.AlreadyExistException; -import com.czertainly.api.exception.AttributeException; -import com.czertainly.api.exception.ConnectorException; import com.czertainly.api.model.client.discovery.DiscoveryDto; import com.czertainly.api.model.client.discovery.DiscoveryHistoryDetailDto; -import com.czertainly.api.model.core.audit.ObjectType; -import com.czertainly.api.model.core.audit.OperationType; import com.czertainly.api.model.core.auth.Resource; import com.czertainly.api.model.core.discovery.DiscoveryStatus; import com.czertainly.api.model.scheduler.SchedulerJobExecutionStatus; -import com.czertainly.core.aop.AuditLogged; -import com.czertainly.core.dao.entity.ScheduledJob; import com.czertainly.core.model.ScheduledTaskResult; import com.czertainly.core.service.DiscoveryService; -import com.czertainly.core.util.AuthHelper; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.transaction.Transactional; import lombok.NoArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.DefaultTransactionDefinition; import java.text.SimpleDateFormat; import java.util.Date; @@ -30,7 +26,7 @@ @Component @NoArgsConstructor @Transactional -public class DiscoveryCertificateTask extends SchedulerJobProcessor { +public class DiscoveryCertificateTask implements ScheduledJobTask { private static final Logger logger = LoggerFactory.getLogger(DiscoveryCertificateTask.class); @@ -38,68 +34,72 @@ public class DiscoveryCertificateTask extends SchedulerJobProcessor { private ObjectMapper mapper = new ObjectMapper(); - private AuthHelper authHelper; + private PlatformTransactionManager transactionManager; private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss.FFF"); @Autowired - public void setDiscoveryService(DiscoveryService discoveryService) { - this.discoveryService = discoveryService; + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; } @Autowired - public void setAuthHelper(AuthHelper authHelper) { - this.authHelper = authHelper; + public void setDiscoveryService(DiscoveryService discoveryService) { + this.discoveryService = discoveryService; } - @Override - String getDefaultJobName() { + public String getDefaultJobName() { return "DiscoveryCertificateTask"; } - @Override - String getDefaultCronExpression() { + public String getDefaultCronExpression() { return null; } - @Override - boolean isDefaultOneTimeJob() { + public boolean isDefaultOneTimeJob() { return false; } - @Override - String getJobClassName() { + public String getJobClassName() { return this.getClass().getName(); } - @Override - boolean systemJob() { + public boolean isSystemJob() { return false; } @Override - @AuditLogged(originator = ObjectType.SCHEDULER, affected = ObjectType.DISCOVERY, operation = OperationType.CREATE) - public ScheduledTaskResult performJob(final String jobName) { - final ScheduledJob scheduledJob = scheduledJobsRepository.findByJobName(jobName); - authHelper.authenticateAsUser(scheduledJob.getUserUuid()); + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public ScheduledTaskResult performJob(final ScheduledJobInfo scheduledJobInfo, final Object taskData) { + final DiscoveryDto discoveryDto = mapper.convertValue(taskData, DiscoveryDto.class); + discoveryDto.setName(discoveryDto.getName() + prepareTimeSuffix()); + // Define a new transaction DiscoveryHistoryDetailDto discovery = null; - final DiscoveryDto discoveryDto = mapper.convertValue(scheduledJob.getObjectData(), DiscoveryDto.class); - discoveryDto.setName(discoveryDto.getName() + prepareTimeSuffix()); + TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { discovery = discoveryService.createDiscovery(discoveryDto, true); - discovery = discoveryService.runDiscovery(UUID.fromString(discovery.getUuid())); - } catch (AlreadyExistException | ConnectorException | AttributeException e) { - final String errorMessage = String.format("Unable to create discovery %s for job %s. Error: %s", discoveryDto.getName(), jobName, e.getMessage()); + transactionManager.commit(status); + } catch (Exception e) { + transactionManager.rollback(status); + final String errorMessage = String.format("Unable to create discovery %s for job %s. Error: %s", discoveryDto.getName(), scheduledJobInfo == null ? "" : scheduledJobInfo.jobName(), e.getMessage()); logger.error(errorMessage); return new ScheduledTaskResult(SchedulerJobExecutionStatus.FAILED, errorMessage, discovery != null ? Resource.DISCOVERY : null, discovery != null ? discovery.getUuid() : null); } - return new ScheduledTaskResult(discovery.getStatus() == DiscoveryStatus.FAILED ? SchedulerJobExecutionStatus.FAILED : SchedulerJobExecutionStatus.SUCCESS, discovery.getMessage(), Resource.DISCOVERY, discovery.getUuid()); + // After the transaction with new discovery persisting commits, run discovery + status = transactionManager.getTransaction(new DefaultTransactionDefinition()); + discovery = discoveryService.runDiscovery(UUID.fromString(discovery.getUuid()), scheduledJobInfo); + transactionManager.commit(status); + + if (discovery.getStatus() != DiscoveryStatus.PROCESSING) { + return new ScheduledTaskResult(discovery.getStatus() == DiscoveryStatus.FAILED ? SchedulerJobExecutionStatus.FAILED : SchedulerJobExecutionStatus.SUCCESS, discovery.getMessage(), Resource.DISCOVERY, discovery.getUuid()); + } + + return null; } private String prepareTimeSuffix() { return "_" + sdf.format(new Date()); } - } diff --git a/src/main/java/com/czertainly/core/tasks/ScheduledJobInfo.java b/src/main/java/com/czertainly/core/tasks/ScheduledJobInfo.java new file mode 100644 index 000000000..0bfa01b2d --- /dev/null +++ b/src/main/java/com/czertainly/core/tasks/ScheduledJobInfo.java @@ -0,0 +1,11 @@ +package com.czertainly.core.tasks; + +import java.io.Serializable; +import java.util.UUID; + +public record ScheduledJobInfo(String jobName, UUID jobUuid, UUID jobHistoryUuid) implements Serializable { + + public ScheduledJobInfo(String jobName) { + this(jobName, null, null); + } +} diff --git a/src/main/java/com/czertainly/core/tasks/ScheduledJobTask.java b/src/main/java/com/czertainly/core/tasks/ScheduledJobTask.java new file mode 100644 index 000000000..c3797de66 --- /dev/null +++ b/src/main/java/com/czertainly/core/tasks/ScheduledJobTask.java @@ -0,0 +1,19 @@ +package com.czertainly.core.tasks; + +import com.czertainly.core.model.ScheduledTaskResult; + +public interface ScheduledJobTask { + + String getDefaultJobName(); + + String getDefaultCronExpression(); + + boolean isDefaultOneTimeJob(); + + String getJobClassName(); + + boolean isSystemJob(); + + ScheduledTaskResult performJob(final ScheduledJobInfo scheduledJobInfo, final Object taskData); + +} diff --git a/src/main/java/com/czertainly/core/tasks/ScheduledTasks.java b/src/main/java/com/czertainly/core/tasks/ScheduledTasks.java deleted file mode 100644 index 76aad3ebb..000000000 --- a/src/main/java/com/czertainly/core/tasks/ScheduledTasks.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.czertainly.core.tasks; - -import com.czertainly.api.exception.SchedulerException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; - -@Configuration -@EnableScheduling -public class ScheduledTasks { - - UpdateCertificateStatusTask updateCertificateStatusTask; - - UpdateIntuneRevocationRequestsTask updateIntuneRevocationRequestsTask; - - @Bean - @ConditionalOnProperty(value = "scheduled-tasks.enabled", matchIfMissing = true, havingValue = "true") - public Void registerJobs() throws SchedulerException { - updateCertificateStatusTask.registerScheduler(); - updateIntuneRevocationRequestsTask.registerScheduler(); - return null; - } - - // SETTERs - - @Autowired - public void setUpdateCertificateStatusTask(UpdateCertificateStatusTask updateCertificateStatusTask) { - this.updateCertificateStatusTask = updateCertificateStatusTask; - } - - @Autowired - public void setUpdateIntuneRevocationRequestsTask(UpdateIntuneRevocationRequestsTask updateIntuneRevocationRequestsTask) { - this.updateIntuneRevocationRequestsTask = updateIntuneRevocationRequestsTask; - } -} diff --git a/src/main/java/com/czertainly/core/tasks/SchedulerJobProcessor.java b/src/main/java/com/czertainly/core/tasks/SchedulerJobProcessor.java deleted file mode 100644 index 9ad17181c..000000000 --- a/src/main/java/com/czertainly/core/tasks/SchedulerJobProcessor.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.czertainly.core.tasks; - -import com.czertainly.api.clients.SchedulerApiClient; -import com.czertainly.api.exception.NotFoundException; -import com.czertainly.api.exception.SchedulerException; -import com.czertainly.api.model.core.auth.Resource; -import com.czertainly.api.model.scheduler.SchedulerJobDto; -import com.czertainly.api.model.scheduler.SchedulerJobExecutionStatus; -import com.czertainly.api.model.scheduler.SchedulerRequestDto; -import com.czertainly.core.dao.entity.ScheduledJob; -import com.czertainly.core.dao.entity.ScheduledJobHistory; -import com.czertainly.core.dao.repository.ScheduledJobHistoryRepository; -import com.czertainly.core.dao.repository.ScheduledJobsRepository; -import com.czertainly.core.messaging.model.NotificationRecipient; -import com.czertainly.core.messaging.producers.NotificationProducer; -import com.czertainly.core.model.ScheduledTaskResult; -import com.czertainly.core.security.authn.CzertainlyUserDetails; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.util.Date; -import java.util.UUID; - -public abstract class SchedulerJobProcessor { - - private Logger logger = LoggerFactory.getLogger(SchedulerJobProcessor.class); - - protected SchedulerApiClient schedulerApiClient; - - protected ScheduledJobsRepository scheduledJobsRepository; - - protected ScheduledJobHistoryRepository scheduledJobHistoryRepository; - - private NotificationProducer notificationProducer; - - @Autowired - public void setScheduledJobsRepository(ScheduledJobsRepository scheduledJobsRepository) { - this.scheduledJobsRepository = scheduledJobsRepository; - } - - @Autowired - public void setSchedulerApiClient(SchedulerApiClient schedulerApiClient) { - this.schedulerApiClient = schedulerApiClient; - } - - @Autowired - public void setScheduledJobHistoryRepository(ScheduledJobHistoryRepository scheduledJobHistoryRepository) { - this.scheduledJobHistoryRepository = scheduledJobHistoryRepository; - } - - @Autowired - public void setNotificationProducer(NotificationProducer notificationProducer) { - this.notificationProducer = notificationProducer; - } - - abstract String getDefaultJobName(); - - abstract String getDefaultCronExpression(); - - abstract boolean isDefaultOneTimeJob(); - - abstract String getJobClassName(); - - abstract boolean systemJob(); - - abstract ScheduledTaskResult performJob(final String jobName); - - public void registerScheduler() throws SchedulerException { - registerScheduler(getDefaultJobName(), getDefaultCronExpression(), isDefaultOneTimeJob()); - } - - public void registerScheduler(final String jobName, final String cronExpression, final boolean oneTime) throws SchedulerException { - registerScheduler(jobName, cronExpression, oneTime, null); - } - - public ScheduledJob registerScheduler(final String jobName, final String cronExpression, final boolean oneTime, final Object objectData) throws SchedulerException { - final SchedulerJobDto schedulerDetail = new SchedulerJobDto(jobName, cronExpression, getJobClassName()); - schedulerApiClient.schedulerCreate(new SchedulerRequestDto(schedulerDetail)); - return saveJobDefinition(jobName, cronExpression, oneTime, objectData); - } - - public void processTask(final String jobName) throws SchedulerException, NotFoundException { - final ScheduledJobHistory scheduledJobHistory = registerJobHistory(jobName); - final ScheduledTaskResult result = performJob(jobName); - updateJobHistory(scheduledJobHistory, result); - checkOneTimeJob(jobName, result.getStatus()); - } - - private void checkOneTimeJob(final String jobName, final SchedulerJobExecutionStatus status) throws SchedulerException { - final ScheduledJob scheduledJob = scheduledJobsRepository.findByJobName(jobName); - - if (!scheduledJob.isSystem()) { - notificationProducer.produceNotificationScheduledJobCompleted(Resource.SCHEDULED_JOB, - scheduledJob.getUuid(), - NotificationRecipient.buildUserNotificationRecipient(scheduledJob.getUserUuid()), - jobName, - scheduledJob.getJobType(), - status.getLabel()); - } - - if (SchedulerJobExecutionStatus.SUCCESS.equals(status) && scheduledJob != null && scheduledJob.isOneTime()) { - schedulerApiClient.deleteScheduledJob(jobName); - logger.info("Scheduled job {} was deleted/unscheduled because it was one time job only.", jobName); - } - } - - private ScheduledJobHistory registerJobHistory(final String jobName) throws NotFoundException { - final ScheduledJob scheduledJob = scheduledJobsRepository.findByJobName(jobName); - if (scheduledJob == null) { - logger.error("There is no such job {} registered.", jobName); - throw new NotFoundException("Scheduled job with name %s not found.".formatted(jobName)); - } - - final ScheduledJobHistory scheduledJobHistory = new ScheduledJobHistory(); - scheduledJobHistory.setScheduledJobUuid(scheduledJob.getUuid()); - scheduledJobHistory.setJobExecution(new Date()); - scheduledJobHistory.setSchedulerExecutionStatus(SchedulerJobExecutionStatus.STARTED); - return scheduledJobHistoryRepository.save(scheduledJobHistory); - } - - private void updateJobHistory(final ScheduledJobHistory scheduledJobHistory, final ScheduledTaskResult result) { - scheduledJobHistory.setJobEndTime(new Date()); - scheduledJobHistory.setSchedulerExecutionStatus(result.getStatus()); - scheduledJobHistory.setResultMessage(result.getResultMessage()); - scheduledJobHistory.setResultObjectType(result.getResultObjectType()); - scheduledJobHistory.setResultObjectIdentification(result.getResultObjectIdentification()); - scheduledJobHistoryRepository.save(scheduledJobHistory); - } - - private ScheduledJob saveJobDefinition(final String jobName, final String cronExpression, final boolean oneTime, final Object objectData) { - ScheduledJob scheduledJob = scheduledJobsRepository.findByJobName(jobName); - if (scheduledJob != null) { - logger.info("Job {} was already registered.", jobName); - return scheduledJob; - } - - scheduledJob = new ScheduledJob(); - scheduledJob.setJobName(jobName); - scheduledJob.setCronExpression(cronExpression); - scheduledJob.setObjectData(objectData); - scheduledJob.setOneTime(oneTime); - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - final String userUuid = ((CzertainlyUserDetails) authentication.getPrincipal()).getUserUuid(); - scheduledJob.setUserUuid(UUID.fromString(userUuid)); - } - scheduledJob.setEnabled(true); - scheduledJob.setSystem(this.systemJob()); - scheduledJob.setJobClassName(this.getJobClassName()); - scheduledJobsRepository.save(scheduledJob); - logger.info("Scheduler job {} was registered.", jobName); - - return scheduledJob; - } - - -} diff --git a/src/main/java/com/czertainly/core/tasks/SystemScheduledJobs.java b/src/main/java/com/czertainly/core/tasks/SystemScheduledJobs.java new file mode 100644 index 000000000..3a441af4c --- /dev/null +++ b/src/main/java/com/czertainly/core/tasks/SystemScheduledJobs.java @@ -0,0 +1,29 @@ +package com.czertainly.core.tasks; + +import com.czertainly.api.exception.SchedulerException; +import com.czertainly.core.service.SchedulerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SystemScheduledJobs { + + private SchedulerService schedulerService; + + @Autowired + public void setSchedulerService(SchedulerService schedulerService) { + this.schedulerService = schedulerService; + } + + @Bean + @ConditionalOnProperty(value = "scheduled-tasks.enabled", matchIfMissing = true, havingValue = "true") + public Void registerJobs() throws SchedulerException { + schedulerService.registerScheduledJob(UpdateCertificateStatusTask.class); + schedulerService.registerScheduledJob(UpdateIntuneRevocationRequestsTask.class); + return null; + } +} diff --git a/src/main/java/com/czertainly/core/tasks/UpdateCertificateStatusTask.java b/src/main/java/com/czertainly/core/tasks/UpdateCertificateStatusTask.java index f9a111ddc..9d778778e 100644 --- a/src/main/java/com/czertainly/core/tasks/UpdateCertificateStatusTask.java +++ b/src/main/java/com/czertainly/core/tasks/UpdateCertificateStatusTask.java @@ -15,7 +15,7 @@ @Component @NoArgsConstructor @Transactional -public class UpdateCertificateStatusTask extends SchedulerJobProcessor { +public class UpdateCertificateStatusTask implements ScheduledJobTask { private static final String JOB_NAME = "updateCertificateStatusJob"; private static final String CRON_EXPRESSION = "0 0 * ? * *"; @@ -23,34 +23,28 @@ public class UpdateCertificateStatusTask extends SchedulerJobProcessor { private ApprovalService approvalService; private CertificateService certificateService; - @Override - String getDefaultJobName() { + public String getDefaultJobName() { return JOB_NAME; } - @Override - String getDefaultCronExpression() { + public String getDefaultCronExpression() { return CRON_EXPRESSION; } - @Override - boolean isDefaultOneTimeJob() { + public boolean isDefaultOneTimeJob() { return false; } - @Override - String getJobClassName() { + public String getJobClassName() { return this.getClass().getName(); } - @Override - boolean systemJob() { + public boolean isSystemJob() { return true; } - @Override @AuditLogged(originator = ObjectType.SCHEDULER, affected = ObjectType.CERTIFICATE, operation = OperationType.UPDATE) - public ScheduledTaskResult performJob(final String jobName) { + public ScheduledTaskResult performJob(final ScheduledJobInfo scheduledJobInfo, final Object taskData) { int certificatesUpdated = certificateService.updateCertificatesStatusScheduled(); int expiredApprovals = approvalService.checkApprovalsExpiration(); diff --git a/src/main/java/com/czertainly/core/tasks/UpdateIntuneRevocationRequestsTask.java b/src/main/java/com/czertainly/core/tasks/UpdateIntuneRevocationRequestsTask.java index bb6a1a8be..3167118b1 100644 --- a/src/main/java/com/czertainly/core/tasks/UpdateIntuneRevocationRequestsTask.java +++ b/src/main/java/com/czertainly/core/tasks/UpdateIntuneRevocationRequestsTask.java @@ -20,7 +20,6 @@ import com.czertainly.core.service.v2.ClientOperationService; import com.czertainly.core.util.AuthHelper; import com.czertainly.core.util.CzertainlyX500NameStyle; -import jakarta.transaction.Transactional; import lombok.NoArgsConstructor; import org.bouncycastle.asn1.x500.X500Name; import org.slf4j.Logger; @@ -29,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import javax.security.auth.x500.X500Principal; import java.util.ArrayList; @@ -39,7 +39,7 @@ @Component @NoArgsConstructor @Transactional -public class UpdateIntuneRevocationRequestsTask extends SchedulerJobProcessor { +public class UpdateIntuneRevocationRequestsTask implements ScheduledJobTask { private static final Logger logger = LoggerFactory.getLogger(UpdateIntuneRevocationRequestsTask.class); @@ -53,13 +53,10 @@ public class UpdateIntuneRevocationRequestsTask extends SchedulerJobProcessor { private static final int MAX_CA_REQUESTS_TO_DOWNLOAD = 500; - @Autowired private ScepProfileRepository scepProfileRepository; - @Autowired private CertificateService certificateService; - @Autowired private ClientOperationService clientOperationService; private AuthHelper authHelper; @@ -69,33 +66,42 @@ public void setAuthHelper(AuthHelper authHelper) { this.authHelper = authHelper; } - @Override - String getDefaultJobName() { + @Autowired + public void setScepProfileRepository(ScepProfileRepository scepProfileRepository) { + this.scepProfileRepository = scepProfileRepository; + } + + @Autowired + public void setCertificateService(CertificateService certificateService) { + this.certificateService = certificateService; + } + + @Autowired + public void setClientOperationService(ClientOperationService clientOperationService) { + this.clientOperationService = clientOperationService; + } + + public String getDefaultJobName() { return JOB_NAME; } - @Override - String getDefaultCronExpression() { + public String getDefaultCronExpression() { return CRON_EXPRESSION; } - @Override - boolean isDefaultOneTimeJob() { + public boolean isDefaultOneTimeJob() { return false; } - @Override - String getJobClassName() { + public String getJobClassName() { return this.getClass().getName(); } - @Override - boolean systemJob() { + public boolean isSystemJob() { return true; } - @Override - public ScheduledTaskResult performJob(String jobName) { + public ScheduledTaskResult performJob(final ScheduledJobInfo scheduledJobInfo, final Object taskData) { logger.info(MarkerFactory.getMarker("scheduleInfo"), "Executing Intune revocation requests update task"); authHelper.authenticateAsSystemUser(AuthHelper.SCEP_USERNAME); @@ -237,7 +243,7 @@ private List processRevocationRequests(List revocationResults) throws Exception { // we upload only when there are some results - if (revocationResults.size() > 0) { + if (!revocationResults.isEmpty()) { String uploadTransactionId = UUID.randomUUID().toString(); if (logger.isDebugEnabled()) { diff --git a/src/test/java/com/czertainly/core/service/DiscoveryServiceTest.java b/src/test/java/com/czertainly/core/service/DiscoveryServiceTest.java index d820b36ef..c5c3841df 100644 --- a/src/test/java/com/czertainly/core/service/DiscoveryServiceTest.java +++ b/src/test/java/com/czertainly/core/service/DiscoveryServiceTest.java @@ -158,14 +158,14 @@ public void testDiscoverCertificates() throws ConnectorException, AlreadyExistEx .willReturn(WireMock.okJson("true"))); // TODO createDiscovery is async - currently not tested properly - discoveryService.runDiscovery(discovery.getUuid()); + discoveryService.runDiscovery(discovery.getUuid(), null); } @Test @Disabled("Async method is not throwing exception") public void testDiscoverCertificates_notFound() { // connector uui not set - Assertions.assertThrows(NotFoundException.class, () -> discoveryService.runDiscovery(discovery.getUuid())); + Assertions.assertThrows(NotFoundException.class, () -> discoveryService.runDiscovery(discovery.getUuid(), null)); } @Test @@ -175,7 +175,7 @@ public void testDiscoverCertificates_validationFailed() throws ConnectorExceptio .post(WireMock.urlPathMatching("/v1/discoveryProvider/[^/]+/attributes/validate")) .willReturn(WireMock.okJson("false"))); - Assertions.assertThrows(ValidationException.class, () -> discoveryService.runDiscovery(discovery.getUuid())); + Assertions.assertThrows(ValidationException.class, () -> discoveryService.runDiscovery(discovery.getUuid(), null)); } @Test diff --git a/src/test/java/com/czertainly/core/service/SchedulerServiceTest.java b/src/test/java/com/czertainly/core/service/SchedulerServiceTest.java new file mode 100644 index 000000000..6d42c287d --- /dev/null +++ b/src/test/java/com/czertainly/core/service/SchedulerServiceTest.java @@ -0,0 +1,301 @@ +package com.czertainly.core.service; + +import com.czertainly.api.exception.*; +import com.czertainly.api.model.client.discovery.DiscoveryCertificateResponseDto; +import com.czertainly.api.model.client.discovery.DiscoveryDto; +import com.czertainly.api.model.common.attribute.v2.AttributeType; +import com.czertainly.api.model.common.attribute.v2.CustomAttribute; +import com.czertainly.api.model.common.attribute.v2.content.AttributeContentType; +import com.czertainly.api.model.common.attribute.v2.properties.CustomAttributeProperties; +import com.czertainly.api.model.core.auth.Resource; +import com.czertainly.api.model.core.certificate.CertificateDetailDto; +import com.czertainly.api.model.core.connector.ConnectorStatus; +import com.czertainly.api.model.core.connector.FunctionGroupCode; +import com.czertainly.api.model.core.discovery.DiscoveryCertificateDto; +import com.czertainly.api.model.core.discovery.DiscoveryStatus; +import com.czertainly.api.model.core.other.ResourceEvent; +import com.czertainly.api.model.core.scheduler.PaginationRequestDto; +import com.czertainly.api.model.core.scheduler.ScheduledJobHistoryResponseDto; +import com.czertainly.api.model.core.search.FilterConditionOperator; +import com.czertainly.api.model.core.search.FilterFieldSource; +import com.czertainly.api.model.core.workflows.*; +import com.czertainly.api.model.scheduler.SchedulerJobExecutionStatus; +import com.czertainly.core.attribute.engine.AttributeEngine; +import com.czertainly.core.dao.entity.*; +import com.czertainly.core.dao.repository.*; +import com.czertainly.core.dao.repository.workflows.TriggerAssociationRepository; +import com.czertainly.core.enums.FilterField; +import com.czertainly.core.security.authz.SecuredUUID; +import com.czertainly.core.security.authz.SecurityFilter; +import com.czertainly.core.tasks.DiscoveryCertificateTask; +import com.czertainly.core.tasks.ScheduledJobInfo; +import com.czertainly.core.util.BaseSpringBootTest; +import com.czertainly.core.util.MetaDefinitions; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.transaction.TestTransaction; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.List; +import java.util.UUID; + +public class SchedulerServiceTest extends BaseSpringBootTest { + + @Autowired + private SchedulerService schedulerService; + @Autowired + private ScheduledJobsRepository scheduledJobsRepository; + @Autowired + private ScheduledJobHistoryRepository scheduledJobHistoryRepository; + + @Autowired + private AttributeEngine attributeEngine; + + @Autowired + private RuleService ruleService; + @Autowired + private ActionService actionService; + @Autowired + private TriggerService triggerService; + @Autowired + private TriggerAssociationRepository triggerAssociationRepository; + + @Autowired + private CertificateService certificateService; + @Autowired + private CertificateRepository certificateRepository; + @Autowired + private CertificateEventHistoryRepository certificateEventHistoryRepository; + + @Autowired + private DiscoveryService discoveryService; + @Autowired + private DiscoveryRepository discoveryRepository; + @Autowired + private DiscoveryCertificateRepository discoveryCertificateRepository; + + @Autowired + private ConnectorRepository connectorRepository; + @Autowired + private FunctionGroupRepository functionGroupRepository; + @Autowired + private Connector2FunctionGroupRepository connector2FunctionGroupRepository; + + @Test + public void runScheduledDiscoveryWithTriggers() throws AlreadyExistException, NotFoundException, AttributeException, SchedulerException, InterruptedException, CertificateException, NoSuchAlgorithmException, RuleException, IOException { + // register custom attribute + CustomAttribute certificateDomainAttr = new CustomAttribute(); + certificateDomainAttr.setUuid(UUID.randomUUID().toString()); + certificateDomainAttr.setName("domain"); + certificateDomainAttr.setType(AttributeType.CUSTOM); + certificateDomainAttr.setContentType(AttributeContentType.STRING); + CustomAttributeProperties customProps = new CustomAttributeProperties(); + customProps.setLabel("Domain of certificate"); + certificateDomainAttr.setProperties(customProps); + attributeEngine.updateCustomAttributeDefinition(certificateDomainAttr, List.of(Resource.CERTIFICATE)); + + // create condition + ConditionItemRequestDto conditionItemRequest = new ConditionItemRequestDto(); + conditionItemRequest.setFieldSource(FilterFieldSource.PROPERTY); + conditionItemRequest.setFieldIdentifier(FilterField.COMMON_NAME.name()); + conditionItemRequest.setOperator(FilterConditionOperator.ENDS_WITH); + conditionItemRequest.setValue(".cz"); + + ConditionRequestDto conditionRequest = new ConditionRequestDto(); + conditionRequest.setName("CommonNameEndsCZCondition"); + conditionRequest.setResource(Resource.CERTIFICATE); + conditionRequest.setType(ConditionType.CHECK_FIELD); + conditionRequest.setItems(List.of(conditionItemRequest)); + ConditionDto condition = ruleService.createCondition(conditionRequest); + + // create rule + RuleRequestDto ruleRequest = new RuleRequestDto(); + ruleRequest.setName("CommonNameEndsCZRule"); + ruleRequest.setResource(Resource.CERTIFICATE); + ruleRequest.setConditionsUuids(List.of(condition.getUuid())); + RuleDetailDto rule = ruleService.createRule(ruleRequest); + + // create execution + ExecutionItemRequestDto executionItemRequest = new ExecutionItemRequestDto(); + executionItemRequest.setFieldSource(FilterFieldSource.CUSTOM); + executionItemRequest.setFieldIdentifier("%s|%s".formatted(certificateDomainAttr.getName(), certificateDomainAttr.getContentType().name())); + executionItemRequest.setData("CZ"); + + ExecutionRequestDto executionRequest = new ExecutionRequestDto(); + executionRequest.setName("CategorizeCertificatesExecution"); + executionRequest.setResource(Resource.CERTIFICATE); + executionRequest.setType(ExecutionType.SET_FIELD); + executionRequest.setItems(List.of(executionItemRequest)); + ExecutionDto execution = actionService.createExecution(executionRequest); + + // create action + ActionRequestDto actionRequest = new ActionRequestDto(); + actionRequest.setName("CategorizeCertificatesAction"); + actionRequest.setResource(Resource.CERTIFICATE); + actionRequest.setExecutionsUuids(List.of(execution.getUuid())); + ActionDetailDto action = actionService.createAction(actionRequest); + + // create trigger + TriggerRequestDto triggerRequest = new TriggerRequestDto(); + triggerRequest.setName("DiscoveryCertificatesCategorization"); + triggerRequest.setType(TriggerType.EVENT); + triggerRequest.setResource(Resource.CERTIFICATE); + triggerRequest.setEvent(ResourceEvent.DISCOVERY_FINISHED); + triggerRequest.setEventResource(Resource.DISCOVERY); + triggerRequest.setRulesUuids(List.of(rule.getUuid())); + triggerRequest.setActionsUuids(List.of(action.getUuid())); + TriggerDetailDto trigger = triggerService.createTrigger(triggerRequest); + + WireMockServer mockServer = new WireMockServer(0); + mockServer.start(); + WireMock.configureFor("localhost", mockServer.port()); + + // create connector + Connector connector = new Connector(); + connector.setName("discoveryProviderConnector"); + connector.setUrl("http://localhost:" + mockServer.port()); + connector.setStatus(ConnectorStatus.CONNECTED); + connector = connectorRepository.save(connector); + + FunctionGroup functionGroup = new FunctionGroup(); + functionGroup.setCode(FunctionGroupCode.DISCOVERY_PROVIDER); + functionGroup.setName(FunctionGroupCode.DISCOVERY_PROVIDER.getCode()); + functionGroupRepository.save(functionGroup); + + Connector2FunctionGroup c2fg = new Connector2FunctionGroup(); + c2fg.setConnector(connector); + c2fg.setFunctionGroup(functionGroup); + c2fg.setKinds(MetaDefinitions.serializeArrayString(List.of("IpAndPort"))); + connector2FunctionGroupRepository.save(c2fg); + + connector.getFunctionGroups().add(c2fg); + connectorRepository.save(connector); + + // create scheduled discovery job + String jobName = "TestScheduledDiscoveryWithTriggers"; + DiscoveryDto discoveryDto = new DiscoveryDto(); + discoveryDto.setName("TestCzertainlyDiscoveryWithTriggers"); + discoveryDto.setKind("IP-Hostname"); + discoveryDto.setConnectorUuid(connector.getUuid().toString()); + discoveryDto.setAttributes(List.of()); + discoveryDto.setTriggers(List.of(UUID.fromString(trigger.getUuid()))); + + ScheduledJob scheduledJobEntity = new ScheduledJob(); + scheduledJobEntity.setJobName(jobName); + scheduledJobEntity.setCronExpression("0 0/3 * * * ? *"); + scheduledJobEntity.setObjectData(discoveryDto); + scheduledJobEntity.setEnabled(true); + scheduledJobEntity.setJobClassName(DiscoveryCertificateTask.class.getName()); + + scheduledJobEntity = scheduledJobsRepository.save(scheduledJobEntity); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + + String discoveredCertificatesMockResponse = """ + { + "status": "completed", + "totalCertificatesDiscovered": 2, + "certificateData": [ + { + "uuid": "0279d416-02ed-4415-a8cd-85af3f083222", + "base64Content": "MIIDyjCCArKgAwIBAgIUULw4BO/gvFzW2wMYXRhmz1kPPdAwDQYJKoZIhvcNAQELBQAwZDEUMBIGA1UEAwwLdGVzdGNlcnQuY3oxCzAJBgNVBAYTAkNaMRgwFgYDVQQIDA9DZW50cmFsIEJvaGVtaWExDzANBgNVBAcMBlNsYW7DvTEUMBIGA1UECgwLM0tleUNvbXBhbnkwHhcNMjQxMDIxMTAzMDEyWhcNMjUxMDIxMTAzMDEyWjBkMRQwEgYDVQQDDAt0ZXN0Y2VydC5jejELMAkGA1UEBhMCQ1oxGDAWBgNVBAgMD0NlbnRyYWwgQm9oZW1pYTEPMA0GA1UEBwwGU2xhbsO9MRQwEgYDVQQKDAszS2V5Q29tcGFueTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ112/a4p9sZ4F2fABLGtSBrbp71n/0uG+H/3usEQU8/FIW644ly5hNl8+SloPWryCCxOl+saXTKv62h0HnE/HNFMKlps4wwWNMsTploFKiAW9AbaDtzNrMy9f/orMoZldDZt5dLX8UR3qMmdK8nlqiJOyCAxIS70OsEQC8fGuIMNYeW6eidXGHjvpqApWnGTyA4U1bJWsDWcOIh/LL2ae9nwTJjVrHthrM6Wq6PplaPxEKYABp51UAQLMzY+cJElcKmwQxiK+zOHns7/ocosZVqI2QyxSmG60icabyrIT6HQHKVNzZHkltmduyYun9YZ+nl68YOuNmtSNi1TLMlfGECAwEAAaN0MHIwHQYDVR0OBBYEFOWFJRXdCer5Bpj+9JrquuJ7e5eQMB8GA1UdIwQYMBaAFOWFJRXdCer5Bpj+9JrquuJ7e5eQMA4GA1UdDwEB/wQEAwIFoDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAA6AWaBFDAWL8oSBCP3q1s2Gq9QhR2QEBZ5tPOMTN5GpIzXxXdm4nHHBK/pSFABUNmrwQMapvq/y6IZ7hNMdC89MTOsHLD0EVPmHHO4xhzMG08XpJdevTrvktjpt0+ju81ratLg34pvJLeLF7ZL5AxwOl6qKX6RgwHpdBUipAYeeVhTVtQ7FLvakKDwYLiN6YFXuM1+CDAK3fsJ6sZki3uRvLYsUi7bguIQCmCQ0/n+T62Driq6mh1FkFB3sgpSFjfEo3bEaaHzF1YZr6otTYPNzcLCStJ5SYNBXKbw7YKAcYavL6yMNTQ2CjmLVnwjjd3O/Sv1kEhZMu86mHeNZK0I=", + "meta": [] + }, + { + "uuid": "ea119f0f-80fb-4d51-aa43-a049f9794a80", + "base64Content": "MIIDzDCCArSgAwIBAgIUZIyXNStxHEmQiOtuYAj7C7fJ9NQwDQYJKoZIhvcNAQELBQAwZTEVMBMGA1UEAwwMdGVzdGNlcnQuY29tMQswCQYDVQQGEwJDWjEYMBYGA1UECAwPQ2VudHJhbCBCb2hlbWlhMQ8wDQYDVQQHDAZTbGFuw70xFDASBgNVBAoMCzNLZXlDb21wYW55MB4XDTI0MTAyMTEwMzUwOFoXDTI1MTAyMTEwMzUwOFowZTEVMBMGA1UEAwwMdGVzdGNlcnQuY29tMQswCQYDVQQGEwJDWjEYMBYGA1UECAwPQ2VudHJhbCBCb2hlbWlhMQ8wDQYDVQQHDAZTbGFuw70xFDASBgNVBAoMCzNLZXlDb21wYW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2CDSXCL/6oE6jOHAxmPMwPSz11P4A4On0R2vo4ff6828lztprQZfdZFpDHiTMi8KRmZWCLtvbwO9inrIB0Ucs0psOuDOOuQdBe6PxmED2jG0NZFXk6N2oz4Ii8HjzDdvkNuPPDSPRrNoHZj4AGp00e5Eap5BXjEyWfp+Q/YO8Jxe96VFRtjZAV2u0/ooX1iu79E9Kuy59dyddGUTP0NCFML21VNp+G2LSRdDjlXPAjpftZ/f2l/1/6V55HI78R2fH801fEgwsPfB60k/LGX5O4f7erbZmTTUxCAq2LUZ4jjbJmuxqI9ExJkI0Oj5f1del6/216VVGZzB3OsaniatfQIDAQABo3QwcjAdBgNVHQ4EFgQUWLvLB6z1rgd0blgV8FPXHYXJQqgwHwYDVR0jBBgwFoAUWLvLB6z1rgd0blgV8FPXHYXJQqgwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEADYJZwsU1ucUOvq45aGZ4T9/4/H8Cy+HE761j+9CB6IV2ARnM5xG9CLSKRaDkzHwwbtREJljCcg1b8ZzjmGwjwBqvHDesWw87oz/6w2CdqEoILnUxkYoLLQ6wRtEKSUZEUzeEwqeVVcvo6TrsVz3dPkoeHubkEhhdaNyOjtbQs2F3JMrjhXsu2vXJ4D7ugFAsMx4w2LsxYLuAeG/njXseca80G+0f8NqFz+q4WpjxNdSY1Z4FrP2OGkVpSjFzJEWwMsdXSKB3QjaL1XW7QSjgefVXA+NPpgzXlFvxo+c4SnoCZ3QwcQIQ+QEEwCwA9Xvw96cFheLinFmLLsuobsNEHQ==", + "meta": [] + } + ], + "meta": [] + } + """; + mockServer.stubFor(WireMock + .get(WireMock.urlPathMatching("/v1/discoveryProvider/[^/]+/attributes")) + .willReturn(WireMock.okJson("[]"))); + mockServer.stubFor(WireMock + .post(WireMock.urlPathMatching("/v1/discoveryProvider/[^/]+/attributes/validate")) + .willReturn(WireMock.okJson("true"))); + mockServer.stubFor(WireMock + .post(WireMock.urlPathMatching("/v1/discoveryProvider/discover")) + .willReturn(WireMock.okJson(discoveredCertificatesMockResponse))); + + mockServer.stubFor(WireMock + .post(WireMock.urlPathMatching("/v1/discoveryProvider/discover/*")) + .willReturn(WireMock.okJson(discoveredCertificatesMockResponse))); + + TestTransaction.start(); + + schedulerService.runScheduledJob(jobName); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + + List discoveries = discoveryRepository.findAll(); + + Assertions.assertEquals(1, discoveries.size()); + + DiscoveryHistory discovery = discoveries.getFirst(); + Assertions.assertEquals(DiscoveryStatus.PROCESSING, discovery.getStatus()); + + ScheduledJobHistory jobHistory = scheduledJobHistoryRepository.findTopByScheduledJobUuidOrderByJobExecutionDesc(scheduledJobEntity.getUuid()); + Assertions.assertNotNull(jobHistory); + + TestTransaction.start(); + + // run manually processing of discovered certificates since RabbitMQ is not available + discoveryService.evaluateDiscoveryTriggers(discovery.getUuid(), UUID.randomUUID(), new ScheduledJobInfo(scheduledJobEntity.getJobName(), scheduledJobEntity.getUuid(), jobHistory.getUuid())); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + + ScheduledJobHistoryResponseDto jobHistoryResponse = schedulerService.getScheduledJobHistory(SecurityFilter.create(), new PaginationRequestDto(), scheduledJobEntity.getUuid().toString()); + + Assertions.assertEquals(1, jobHistoryResponse.getScheduledJobHistory().size()); + Assertions.assertEquals(SchedulerJobExecutionStatus.SUCCESS, jobHistoryResponse.getScheduledJobHistory().getFirst().getStatus()); + + // assert triggers evaluation + TriggerHistorySummaryDto triggerSummary = triggerService.getTriggerHistorySummary(discovery.getUuid().toString()); + Assertions.assertEquals(2, triggerSummary.getObjectsEvaluated()); + Assertions.assertEquals(1, triggerSummary.getObjectsMatched()); + + DiscoveryCertificateResponseDto discoveredCertificates = discoveryService.getDiscoveryCertificates(discovery.getSecuredUuid(), null, 10, 1); + Assertions.assertEquals(2, discoveredCertificates.getCertificates().size()); + + boolean matched = false; + for (DiscoveryCertificateDto discoveryCertificate : discoveredCertificates.getCertificates()) { + if (discoveryCertificate.getCommonName().endsWith(".cz")) { + CertificateDetailDto certificateDetailDto = certificateService.getCertificate(SecuredUUID.fromString(discoveryCertificate.getInventoryUuid())); + + matched = true; + Assertions.assertEquals(1, certificateDetailDto.getCustomAttributes().size()); + Assertions.assertEquals("CZ", certificateDetailDto.getCustomAttributes().getFirst().getContent().getFirst().getData()); + } + } + Assertions.assertTrue(matched); + + // cleanup + TestTransaction.start(); + + triggerAssociationRepository.deleteAll(); + discoveryCertificateRepository.deleteAll(); + discoveryRepository.deleteAll(); + certificateEventHistoryRepository.deleteAll(); + certificateRepository.deleteAll(); + attributeEngine.deleteAttributeDefinition(AttributeType.CUSTOM, UUID.fromString(certificateDomainAttr.getUuid())); + connector2FunctionGroupRepository.deleteAll(); + functionGroupRepository.deleteAll(); + connectorRepository.deleteAll(); + scheduledJobHistoryRepository.deleteAll(); + scheduledJobsRepository.deleteAll(); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + } +} From 1fa6f3f32cec74fb4dbfb4044d838a8145dd721c Mon Sep 17 00:00:00 2001 From: lubomirw <76479559+lubomirw@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:45:23 +0100 Subject: [PATCH 09/12] Register metadata content for discovered certificates before its processing (#875) --- .../attribute/engine/AttributeEngine.java | 21 ++++++++++++++----- .../AttributeContentItemRepository.java | 2 +- .../service/handler/CertificateHandler.java | 7 +++++-- .../service/impl/DiscoveryServiceImpl.java | 13 ++++++++---- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/czertainly/core/attribute/engine/AttributeEngine.java b/src/main/java/com/czertainly/core/attribute/engine/AttributeEngine.java index 252d7278e..ff61ac582 100644 --- a/src/main/java/com/czertainly/core/attribute/engine/AttributeEngine.java +++ b/src/main/java/com/czertainly/core/attribute/engine/AttributeEngine.java @@ -478,6 +478,20 @@ public List getDefinitionObjectAttributeContent(AttributeType att return mapping.values().stream().toList(); } + public void registerAttributeContentItems(UUID attributeDefinitionUuid, Collection attributeContentItems) { + for (BaseAttributeContent attributeContentItem : attributeContentItems) { + AttributeContentItem contentItemEntity = attributeContentItemRepository.findByJsonAndAttributeDefinitionUuid(attributeContentItem, attributeDefinitionUuid); + + // check if content item for this attribute definition exists to don't create duplicate items + if (contentItemEntity == null) { + contentItemEntity = new AttributeContentItem(); + contentItemEntity.setJson(attributeContentItem); + contentItemEntity.setAttributeDefinitionUuid(attributeDefinitionUuid); + attributeContentItemRepository.save(contentItemEntity); + } + } + } + public List getObjectCustomAttributesContent(Resource objectType, UUID objectUuid) { logger.debug("Getting the custom attributes for {} with UUID: {}", objectType.getLabel(), objectUuid); SecurityResourceFilter securityResourceFilter = loadCustomAttributesSecurityResourceFilter(); @@ -839,14 +853,11 @@ private void createObjectAttributeContent(AttributeDefinition attributeDefinitio validateAttributeContent(attributeDefinition, attributeContentItems); for (int i = 0; i < attributeContentItems.size(); i++) { - AttributeContentItem contentItemEntity; BaseAttributeContent attributeContentItem = attributeContentItems.get(i); - Optional contentItemEntityResponse = attributeContentItemRepository.findByJsonAndAttributeDefinitionUuid(attributeContentItem, attributeDefinition.getUuid()); + AttributeContentItem contentItemEntity = attributeContentItemRepository.findByJsonAndAttributeDefinitionUuid(attributeContentItem, attributeDefinition.getUuid()); // check if content item for this attribute definition exists to don't create duplicate items - if (contentItemEntityResponse.isPresent()) { - contentItemEntity = contentItemEntityResponse.get(); - + if (contentItemEntity != null) { // check if that content item is not already assigned to same object for meta attribute // TODO: do we need to allow duplicate content items for one attribute definition? Maybe if attribute is list or do this check just for META attributes? var aco = attributeContent2ObjectRepository.getByConnectorUuidAndAttributeContentItemUuidAndObjectTypeAndObjectUuidAndSourceObjectTypeAndSourceObjectUuid(objectAttributeContentInfo.connectorUuid(), contentItemEntity.getUuid(), objectAttributeContentInfo.objectType(), objectAttributeContentInfo.objectUuid(), objectAttributeContentInfo.sourceObjectType(), objectAttributeContentInfo.sourceObjectUuid()); diff --git a/src/main/java/com/czertainly/core/dao/repository/AttributeContentItemRepository.java b/src/main/java/com/czertainly/core/dao/repository/AttributeContentItemRepository.java index d57e0ec0b..e7c116367 100644 --- a/src/main/java/com/czertainly/core/dao/repository/AttributeContentItemRepository.java +++ b/src/main/java/com/czertainly/core/dao/repository/AttributeContentItemRepository.java @@ -12,7 +12,7 @@ @Repository public interface AttributeContentItemRepository extends JpaRepository { - Optional findByJsonAndAttributeDefinitionUuid(BaseAttributeContent attributeContent, UUID definitionUuid); + AttributeContentItem findByJsonAndAttributeDefinitionUuid(BaseAttributeContent attributeContent, UUID definitionUuid); long deleteByAttributeDefinitionUuid(UUID definitionUuid); long deleteByAttributeDefinitionTypeAndAttributeDefinitionConnectorUuid(AttributeType attributeType, UUID connectorUuid); diff --git a/src/main/java/com/czertainly/core/service/handler/CertificateHandler.java b/src/main/java/com/czertainly/core/service/handler/CertificateHandler.java index 3e617cc4b..9af007e9f 100644 --- a/src/main/java/com/czertainly/core/service/handler/CertificateHandler.java +++ b/src/main/java/com/czertainly/core/service/handler/CertificateHandler.java @@ -5,12 +5,14 @@ import com.czertainly.api.exception.NotFoundException; import com.czertainly.api.exception.RuleException; import com.czertainly.api.model.common.attribute.v2.MetadataAttribute; +import com.czertainly.api.model.common.attribute.v2.content.BaseAttributeContent; import com.czertainly.api.model.connector.discovery.DiscoveryProviderCertificateDataDto; import com.czertainly.api.model.core.auth.Resource; import com.czertainly.api.model.core.certificate.CertificateEvent; import com.czertainly.api.model.core.certificate.CertificateEventStatus; import com.czertainly.core.attribute.engine.AttributeEngine; import com.czertainly.core.attribute.engine.records.ObjectAttributeContentInfo; +import com.czertainly.core.dao.entity.AttributeDefinition; import com.czertainly.core.dao.entity.Certificate; import com.czertainly.core.dao.entity.DiscoveryCertificate; import com.czertainly.core.dao.entity.DiscoveryHistory; @@ -135,11 +137,12 @@ public void validate(Certificate certificate) { } @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT) - public void updateMetadataDefinition(List metadataAttributes, UUID connectorUuid, String connectorName) { + public void updateMetadataDefinition(List metadataAttributes, Map> metadataContentsMapping, UUID connectorUuid, String connectorName) { logger.debug("Updating {} discovery certificate metadata definitions for connector {}", metadataAttributes.size(), connectorName); for (MetadataAttribute metadataAttribute : metadataAttributes) { try { - attributeEngine.updateMetadataAttributeDefinition(metadataAttribute, connectorUuid); + AttributeDefinition attributeDefinition = attributeEngine.updateMetadataAttributeDefinition(metadataAttribute, connectorUuid); + attributeEngine.registerAttributeContentItems(attributeDefinition.getUuid(), metadataContentsMapping.get(metadataAttribute.getUuid())); } catch (AttributeException e) { logger.error("Unable to update discovery certificate metadata definition with UUID {} and name {} for discovery connector {}. Message: {}", metadataAttribute.getUuid(), metadataAttribute.getName(), connectorName, e.getMessage(), e); } diff --git a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java index 8a232bfb1..56aafa284 100644 --- a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java @@ -11,6 +11,7 @@ import com.czertainly.api.model.common.NameAndUuidDto; import com.czertainly.api.model.common.attribute.v2.AttributeType; import com.czertainly.api.model.common.attribute.v2.MetadataAttribute; +import com.czertainly.api.model.common.attribute.v2.content.BaseAttributeContent; import com.czertainly.api.model.connector.discovery.DiscoveryDataRequestDto; import com.czertainly.api.model.connector.discovery.DiscoveryProviderCertificateDataDto; import com.czertainly.api.model.connector.discovery.DiscoveryProviderDto; @@ -564,8 +565,8 @@ private List downloadDiscoveredCertificates private Future downloadDiscoveredCertificatesBatchAsync(final DiscoveryHistory discovery, final DiscoveryProviderDto response, final Connector connector, final Set uniqueCertificateContents, final List duplicateCertificates, final ExecutorService executor, final int currentPage) { // categorize certs and collect metadata definitions - Set certMetadataUuids = new HashSet<>(); List metadataDefinitions = new ArrayList<>(); + Map> metadataContentsMapping = new HashMap<>(); List discoveredCertificates = new ArrayList<>(); response.getCertificateData().forEach(c -> { if (uniqueCertificateContents.contains(c.getBase64Content())) { @@ -576,15 +577,19 @@ private Future downloadDiscoveredCertificatesBatchAsync(final DiscoveryHistor } for (MetadataAttribute m : c.getMeta()) { - if (!certMetadataUuids.contains(m.getUuid())) { + Set metadataContents = metadataContentsMapping.get(m.getUuid()); + if (metadataContents == null) { metadataDefinitions.add(m); - certMetadataUuids.add(m.getUuid()); + metadataContents = new HashSet<>(); + metadataContentsMapping.put(m.getUuid(), metadataContents); } + + metadataContents.addAll(m.getContent()); } }); // add/update certificate metadata to prevent creating duplicate definitions in parallel processing - certificateHandler.updateMetadataDefinition(metadataDefinitions, connector.getUuid(), connector.getName()); + certificateHandler.updateMetadataDefinition(metadataDefinitions, metadataContentsMapping, connector.getUuid(), connector.getName()); // run in separate virtual thread and continue return executor.submit(() -> { From d1ca1173f8b49de4b4c7ab7bbc808193dd4bb7f2 Mon Sep 17 00:00:00 2001 From: klaraf755 <80590912+klaraf755@users.noreply.github.com> Date: Sun, 3 Nov 2024 10:08:18 +0100 Subject: [PATCH 10/12] Fix bulk removal of certificate owner (#876) --- .../api/web/CertificateControllerImpl.java | 2 +- .../core/service/CertificateService.java | 4 +- .../service/impl/CertificateServiceImpl.java | 133 ++++++------------ 3 files changed, 46 insertions(+), 93 deletions(-) diff --git a/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java b/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java index 1cc2caf4a..82a35a993 100644 --- a/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java +++ b/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java @@ -85,7 +85,7 @@ public void bulkUpdateCertificateObjects(MultipleCertificateObjectUpdateDto requ if (request.getFilters() != null && !request.getFilters().isEmpty() && (request.getCertificateUuids() == null || request.getCertificateUuids().isEmpty())) { throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); } - certificateService.bulkUpdateCertificateObjects(SecurityFilter.create(), request); + certificateService.bulkUpdateCertificatesObjects(SecurityFilter.create(), request); } @Override diff --git a/src/main/java/com/czertainly/core/service/CertificateService.java b/src/main/java/com/czertainly/core/service/CertificateService.java index 3fffc967e..8ed07085a 100644 --- a/src/main/java/com/czertainly/core/service/CertificateService.java +++ b/src/main/java/com/czertainly/core/service/CertificateService.java @@ -42,7 +42,7 @@ public interface CertificateService extends ResourceExtensionService { Certificate getCertificateEntityByIssuerDnNormalizedAndSerialNumber(String issuerDn, String serialNumber) throws NotFoundException; - Boolean checkCertificateExistsByFingerprint(String fingerprint); + boolean checkCertificateExistsByFingerprint(String fingerprint); void deleteCertificate(SecuredUUID uuid) throws NotFoundException; @@ -164,7 +164,7 @@ public interface CertificateService extends ResourceExtensionService { * * @param request Request to update multiple objects */ - void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException; + void bulkUpdateCertificatesObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException; /** * Function to update status of certificates by scheduled event diff --git a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java index 4967e0dd6..4fec88b8c 100644 --- a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java @@ -100,12 +100,6 @@ @Transactional public class CertificateServiceImpl implements CertificateService { - // Default page size for the certificate search API when page size is not provided - public static final Integer DEFAULT_PAGE_SIZE = 10; - // Maximum page size for search API operation - public static final Integer MAX_PAGE_SIZE = 1000; - // Default batch size to perform bulk delete operation on Certificates - public static final Integer DELETE_BATCH_SIZE = 1000; private static final String UNDEFINED_CERTIFICATE_OBJECT_NAME = "undefined"; private static final Logger logger = LoggerFactory.getLogger(CertificateServiceImpl.class); @@ -301,7 +295,7 @@ public Certificate getCertificateEntityByIssuerDnNormalizedAndSerialNumber(Strin } @Override - public Boolean checkCertificateExistsByFingerprint(String fingerprint) { + public boolean checkCertificateExistsByFingerprint(String fingerprint) { try { return certificateRepository.findByFingerprint(fingerprint).isPresent(); } catch (Exception e) { @@ -372,20 +366,51 @@ private void updateTrustedCaMark(SecuredUUID uuid, Boolean trustedCa) throws Not @Override @AuditLogged(originator = ObjectType.FE, affected = ObjectType.CERTIFICATE, operation = OperationType.CHANGE) @ExternalAuthorization(resource = Resource.CERTIFICATE, action = ResourceAction.UPDATE, parentResource = Resource.RA_PROFILE, parentAction = ResourceAction.DETAIL) - public void bulkUpdateCertificateObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public void bulkUpdateCertificatesObjects(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { logger.info("Bulk updating certificate objects: RA {} groups {} owner {}", request.getRaProfileUuid(), request.getGroupUuids(), request.getOwnerUuid()); setupSecurityFilter(filter); - if (request.getRaProfileUuid() != null) { - bulkUpdateRaProfile(filter, request); + Set groupUuids = null; + if (request.getGroupUuids() != null) + groupUuids = request.getGroupUuids().stream().map(UUID::fromString).collect(Collectors.toSet()); + String ownerUuid = null; + if (request.getOwnerUuid() != null && !request.getOwnerUuid().isEmpty()) { + ownerUuid = request.getOwnerUuid(); } - if (request.getGroupUuids() != null) { - bulkUpdateCertificateGroup(filter, request); + + boolean removeRaProfile = false; + if (request.getRaProfileUuid() != null) removeRaProfile = request.getRaProfileUuid().isEmpty(); + + if (request.getFilters() != null && !request.getFilters().isEmpty() && (request.getCertificateUuids() == null || request.getCertificateUuids().isEmpty())) { + throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); } - if (request.getOwnerUuid() != null) { - bulkUpdateOwner(filter, request); + + UUID loggedUserUuid = null; + for (String certificateUuidString : request.getCertificateUuids()) { + SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString); + TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + bulkUpdateCertificateObjects(request, certificateUuid, groupUuids, ownerUuid, removeRaProfile); + transactionManager.commit(status); + } catch (Exception e) { + transactionManager.rollback(status); + logger.error("Error occurred when updating certificate with UUID {}: {}", certificateUuidString, e.getMessage()); + if (loggedUserUuid == null) { + loggedUserUuid = UUID.fromString(AuthHelper.getUserIdentification().getUuid()); + } + notificationProducer.produceNotificationText(Resource.CERTIFICATE, certificateUuid.getValue(), NotificationRecipient.buildUserNotificationRecipient(loggedUserUuid), "Unable to update properties of the certificate " + certificateUuid, e.getMessage()); + } } } + private void bulkUpdateCertificateObjects(MultipleCertificateObjectUpdateDto request, SecuredUUID certificateUuid, Set groupUuids, String ownerUuid, boolean removeRaProfile) throws NotFoundException, CertificateOperationException, AttributeException { + permissionEvaluator.certificate(certificateUuid); + if (groupUuids != null) updateCertificateGroups(certificateUuid, groupUuids); + if (request.getOwnerUuid() != null) updateOwner(certificateUuid, ownerUuid); + if (request.getRaProfileUuid() != null) + switchRaProfile(certificateUuid, removeRaProfile ? null : SecuredUUID.fromString(request.getRaProfileUuid())); + } + @Override @AuditLogged(originator = ObjectType.FE, affected = ObjectType.CERTIFICATE, operation = OperationType.DELETE) @Async @@ -1513,11 +1538,11 @@ public void switchRaProfile(SecuredUUID uuid, SecuredUUID raProfileUuid) throws try { response = certificateApiClient.identifyCertificate(newRaProfile.getAuthorityInstanceReference().getConnector().mapToDto(), newRaProfile.getAuthorityInstanceReference().getAuthorityInstanceUuid(), requestDto); } catch (ConnectorException e) { - certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.FAILED, String.format("Certificate not identified by authority of new RA profile %s. Certificate needs to be reissued.", newRaProfile.getName()), ""); - throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate not identified by authority of new RA profile %s. Certificate: %s", newRaProfile.getName(), certificate)); + eventProducer.produceCertificateEventMessage(uuid.getValue(), CertificateEvent.UPDATE_RA_PROFILE.getCode(), CertificateEventStatus.FAILED.toString(), String.format("Certificate not identified by authority of new RA profile %s. Certificate needs to be reissued.", newRaProfile.getName()), null); + throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate not identified by authority of new RA profile %s. Certificate: %s", newRaProfile.getName(), certificate.toStringShort())); } catch (ValidationException e) { - certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.FAILED, String.format("Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate needs to be reissued.", newRaProfile.getName()), ""); - throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate: %s", newRaProfile.getName(), certificate)); + eventProducer.produceCertificateEventMessage(uuid.getValue(), CertificateEvent.UPDATE_RA_PROFILE.getCode(), CertificateEventStatus.FAILED.toString(), String.format("Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate needs to be reissued.", newRaProfile.getName()), null); + throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate: %s", newRaProfile.getName(), certificate.toStringShort())); } } @@ -1586,78 +1611,6 @@ public void updateOwner(SecuredUUID uuid, String ownerUuid) throws NotFoundExcep certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, "%s -> %s".formatted(currentOwnerName, newOwnerName == null ? UNDEFINED_CERTIFICATE_OBJECT_NAME : newOwnerName), ""); } - private void bulkUpdateRaProfile(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { - boolean removeRaProfile = request.getRaProfileUuid().isEmpty(); - if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) { - for (String certificateUuidString : request.getCertificateUuids()) { - try { - SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString); - permissionEvaluator.certificate(certificateUuid); - switchRaProfile(certificateUuid, removeRaProfile ? null : SecuredUUID.fromString(request.getRaProfileUuid())); - } catch (CertificateOperationException e) { - logger.warn(e.getMessage()); - } catch (AttributeException e) { - logger.warn("Certificate {} switched but there was issue with updating attributes: {}", certificateUuidString, e.getMessage()); - } - } - } else { - throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); - } - } - - private void bulkUpdateCertificateGroup(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException { - if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) { - Set groupUuids = request.getGroupUuids().stream().map(UUID::fromString).collect(Collectors.toSet()); - for (String certificateUuidString : request.getCertificateUuids()) { - SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString); - permissionEvaluator.certificate(certificateUuid); - this.updateCertificateGroups(certificateUuid, groupUuids); - } - } - // updating group by filters not supported now -// else { -// } - } - - private void bulkUpdateOwner(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException, NotSupportedException { - boolean removeOwner = request.getOwnerUuid().isEmpty(); - String ownerUuid = null; - String ownerName = null; - if (!removeOwner) { - ownerUuid = request.getOwnerUuid(); - } - List batchHistoryOperationList = new ArrayList<>(); - if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) { - List batchOperationList = new ArrayList<>(); - for (String certificateUuidString : request.getCertificateUuids()) { - SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString); - permissionEvaluator.certificate(certificateUuid); - updateOwner(certificateUuid, ownerUuid); - } - certificateRepository.saveAll(batchOperationList); - certificateEventHistoryService.asyncSaveAllInBatch(batchHistoryOperationList); - } else { - throw new NotSupportedException("Bulk updating of certificates by filters is not supported."); } - } - - private List> partitionList(List fullList) { - List> certificates = new ArrayList<>(); - - for (int i = 0; i < fullList.size(); i += DELETE_BATCH_SIZE) { - certificates.add(fullList.subList(i, Math.min(i + DELETE_BATCH_SIZE, fullList.size()))); - } - return certificates; - } - - private List> partitionContents(List fullList) { - List> certificates = new ArrayList<>(); - - for (int i = 0; i < fullList.size(); i += DELETE_BATCH_SIZE) { - certificates.add(fullList.subList(i, Math.min(i + DELETE_BATCH_SIZE, fullList.size()))); - } - return certificates; - } - private void setupSecurityFilter(SecurityFilter filter) { filter.setParentRefProperty("raProfileUuid"); } From 7a01ddffd4dbac3081188cc435438b2f954788f2 Mon Sep 17 00:00:00 2001 From: 3keyroman <46850604+3keyroman@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:57:12 +0100 Subject: [PATCH 11/12] Fix ACME finding an account URL given a key (#880) --- .../service/acme/impl/AcmeServiceImpl.java | 21 ++++++++- .../core/service/AcmeServiceTest.java | 43 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/czertainly/core/service/acme/impl/AcmeServiceImpl.java b/src/main/java/com/czertainly/core/service/acme/impl/AcmeServiceImpl.java index 6b27f6fe6..936cf6bac 100644 --- a/src/main/java/com/czertainly/core/service/acme/impl/AcmeServiceImpl.java +++ b/src/main/java/com/czertainly/core/service/acme/impl/AcmeServiceImpl.java @@ -214,8 +214,25 @@ public ResponseEntity newAccount(String acmeProfileName, String request validateRequest(jwsRequest, acmeProfileName, requestUri, isRaProfileBased); NewAccountRequest accountRequest = AcmeJsonProcessor.getPayloadAsRequestObject(jwsRequest.getJwsObject(), NewAccountRequest.class); - logger.debug("New Account requested: {}", accountRequest.toString()); - AcmeAccount account = addNewAccount(acmeProfileName, AcmePublicKeyProcessor.publicKeyPemStringFromObject(jwsRequest.getPublicKey()), accountRequest, isRaProfileBased); + logger.debug("New Account request: {}", accountRequest.toString()); + + // Check if the Account already exists + AcmeAccount account = acmeAccountRepository.findByPublicKey(AcmePublicKeyProcessor.publicKeyPemStringFromObject(jwsRequest.getPublicKey())); + + if (accountRequest.isOnlyReturnExisting()) { + logger.debug("Request to only return existing Account"); + if (account == null) { + logger.error("Requested Account does not exists"); + throw new AcmeProblemDocumentException(HttpStatus.BAD_REQUEST, Problem.ACCOUNT_DOES_NOT_EXIST); + } + } else { + // Create a new Account if it does not exist + if (account == null) { + logger.debug("Request to create a new Account"); + account = addNewAccount(acmeProfileName, AcmePublicKeyProcessor.publicKeyPemStringFromObject(jwsRequest.getPublicKey()), accountRequest, isRaProfileBased); + } + } + Account accountDto = account.mapToDto(); String baseUri = getAcmeBaseUri(); diff --git a/src/test/java/com/czertainly/core/service/AcmeServiceTest.java b/src/test/java/com/czertainly/core/service/AcmeServiceTest.java index 475502e11..e1788d797 100644 --- a/src/test/java/com/czertainly/core/service/AcmeServiceTest.java +++ b/src/test/java/com/czertainly/core/service/AcmeServiceTest.java @@ -169,6 +169,7 @@ public void setUp() throws JOSEException, NoSuchAlgorithmException, CertificateE acmeProfile.setTermsOfServiceChangeUrl("change url"); acmeProfile.setEnabled(true); acmeProfile.setDisableNewOrders(false); + acmeProfile.setRequireContact(true); acmeProfileRepository.save(acmeProfile); raProfile.setAcmeProfile(acmeProfile); @@ -322,6 +323,48 @@ private String buildNewAccountRequestJSON_fail() throws JOSEException { return jwsObjectJSON.serializeFlattened(); } + @Test + public void testOnlyReturnExistingAccount() throws URISyntaxException, JOSEException, AcmeProblemDocumentException, NotFoundException { + URI requestUri = new URI(RA_BASE_URI + RA_PROFILE_NAME + "/new-account"); + ResponseEntity account = acmeService.newAccount(RA_PROFILE_NAME, buildOnlyReturnExistingAccountJSON(requestUri), requestUri, true); + Assertions.assertEquals(HttpStatus.OK, account.getStatusCode()); + Assertions.assertNotNull(account); + Assertions.assertEquals(AccountStatus.VALID, Objects.requireNonNull(account.getBody()).getStatus()); + } + + private String buildOnlyReturnExistingAccountJSON(URI requestUri) throws JOSEException { + JWSObjectJSON jwsObjectJSON = new JWSObjectJSON(new Payload("{\"onlyReturnExisting\":true}")); + jwsObjectJSON.sign( + new JWSHeader.Builder(JWSAlgorithm.RS256) + .jwk(rsa2048PublicJWK) + .customParam(NONCE_HEADER_CUSTOM_PARAM, acmeValidNonce.getNonce()) + .customParam(URL_HEADER_CUSTOM_PARAM, requestUri.toString()) + .build(), + rsa2048Signer + ); + return jwsObjectJSON.serializeFlattened(); + } + + @Test + public void testOnlyReturnExistingAccount_fail() throws URISyntaxException, JOSEException, AcmeProblemDocumentException, NotFoundException { + URI requestUri = new URI(RA_BASE_URI + RA_PROFILE_NAME + "/new-account"); + Assertions.assertThrows(AcmeProblemDocumentException.class, + () -> acmeService.newAccount(RA_PROFILE_NAME, buildOnlyReturnExistingAccountJSON_fail(requestUri), requestUri, true)); + } + + private String buildOnlyReturnExistingAccountJSON_fail(URI requestUri) throws JOSEException { + JWSObjectJSON jwsObjectJSON = new JWSObjectJSON(new Payload("{\"onlyReturnExisting\":true}")); + jwsObjectJSON.sign( + new JWSHeader.Builder(JWSAlgorithm.RS256) + .jwk(newRsa2048PublicJWK) + .customParam(NONCE_HEADER_CUSTOM_PARAM, acmeValidNonce.getNonce()) + .customParam(URL_HEADER_CUSTOM_PARAM, requestUri.toString()) + .build(), + newRsa2048Signer + ); + return jwsObjectJSON.serializeFlattened(); + } + @Test public void testNewOrder() throws JOSEException, URISyntaxException, AcmeProblemDocumentException, NotFoundException { URI requestUri = new URI(BASE_URI + ACME_PROFILE_NAME + "/new-order"); From dfc7707fff0024bbbf02543581f538f86f2abdee Mon Sep 17 00:00:00 2001 From: 3keyroman <46850604+3keyroman@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:05:12 +0100 Subject: [PATCH 12/12] Updates for release 2.13.1 (#881) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 91e339bfa..64405fb69 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ core - 2.13.1-SNAPSHOT + 2.13.1 CZERTAINLY-Core @@ -49,7 +49,7 @@ com.czertainly interfaces - 2.13.1-SNAPSHOT + 2.13.1