This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push: new f9e4c8da6b [SYNCOPE-1697] Improving input and validation of CommandArgs instances (#395) f9e4c8da6b is described below commit f9e4c8da6ba71d316b780dec9a285deaffc85b5e Author: Francesco Chicchiriccò <ilgro...@users.noreply.github.com> AuthorDate: Tue Nov 29 15:16:34 2022 +0100 [SYNCOPE-1697] Improving input and validation of CommandArgs instances (#395) --- .../syncope/client/console/panels/BeanPanel.java | 128 ++++++++++++++++----- .../syncope/client/console/panels/BeanPanel.html | 14 ++- .../common/lib/types/EntityViolationType.java | 72 +++++++----- .../apache/syncope/core/logic/CommandLogic.java | 25 +++- .../syncope/core/logic/IdRepoLogicContext.java | 5 +- .../core/logic/job/MacroRunJobDelegate.java | 17 ++- .../validation/InvalidEntityException.java | 27 ++--- .../fit/core/reference/TestCommandArgs.java | 15 ++- .../org/apache/syncope/fit/core/CommandITCase.java | 17 +++ .../org/apache/syncope/fit/core/MacroITCase.java | 16 ++- .../apache/syncope/fit/core/MembershipITCase.java | 3 +- pom.xml | 2 +- 12 files changed, 248 insertions(+), 93 deletions(-) diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java index 6e6d5b028a..bb945460b5 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/BeanPanel.java @@ -18,6 +18,10 @@ */ package org.apache.syncope.client.console.panels; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverBehavior; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig; +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig; +import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; @@ -31,6 +35,7 @@ import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.SyncopeWebApplication; import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel; @@ -50,7 +55,6 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel; import org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel; import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel; import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel; -import org.apache.syncope.common.lib.Schema; import org.apache.syncope.common.lib.report.SearchCondition; import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder; import org.apache.syncope.common.lib.to.SchemaTO; @@ -61,9 +65,11 @@ import org.apache.wicket.core.util.lang.PropertyResolverConverter; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.util.ListModel; @@ -126,9 +132,37 @@ public class BeanPanel<T extends Serializable> extends Panel { private static final long serialVersionUID = 9101744072914090143L; + private void setRequired(final ListItem<String> item, final boolean required) { + if (required) { + Fragment fragment = new Fragment("required", "requiredFragment", this); + fragment.add(new Label("requiredLabel", "*")); + item.replace(fragment); + } + } + + private void setDescription(final ListItem<String> item, final String description) { + Fragment fragment = new Fragment("description", "descriptionFragment", this); + fragment.add(new Label("descriptionLabel", Model.of()).add(new PopoverBehavior( + Model.<String>of(), + Model.of(description), + new PopoverConfig().withPlacement(TooltipConfig.Placement.right)) { + + private static final long serialVersionUID = -7867802555691605021L; + + @Override + protected String createRelAttribute() { + return "description"; + } + }).setRenderBodyOnly(false)); + item.replace(fragment); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected void populateItem(final ListItem<String> item) { + item.add(new Fragment("required", "emptyFragment", this)); + item.add(new Fragment("description", "emptyFragment", this)); + String fieldName = item.getModelObject(); item.add(new Label("fieldName", new ResourceModel(fieldName, fieldName))); @@ -138,13 +172,11 @@ public class BeanPanel<T extends Serializable> extends Panel { return; } - SearchCondition scondAnnot = field.getAnnotation(SearchCondition.class); - Schema schemaAnnot = field.getAnnotation(Schema.class); - BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean.getObject()); Panel panel; + SearchCondition scondAnnot = field.getAnnotation(SearchCondition.class); if (scondAnnot != null) { String fiql = (String) wrapper.getPropertyValue(fieldName); @@ -171,32 +203,33 @@ public class BeanPanel<T extends Serializable> extends Panel { builder = SyncopeClient.getAnyObjectSearchConditionBuilder(scondAnnot.type()); } - if (BeanPanel.this.sCondWrapper != null) { - BeanPanel.this.sCondWrapper.put(fieldName, Pair.of(builder, clauses)); - } + Optional.ofNullable(BeanPanel.this.sCondWrapper). + ifPresent(scw -> scw.put(fieldName, Pair.of(builder, clauses))); } else if (List.class.equals(field.getType())) { Class<?> listItemType = field.getGenericType() instanceof ParameterizedType ? (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0] : String.class; - if (listItemType.equals(String.class) && schemaAnnot != null) { + org.apache.syncope.common.lib.Schema schema = + field.getAnnotation(org.apache.syncope.common.lib.Schema.class); + if (listItemType.equals(String.class) && schema != null) { List<SchemaTO> choices = new ArrayList<>(); - for (SchemaType type : schemaAnnot.type()) { + for (SchemaType type : schema.type()) { switch (type) { case PLAIN: choices.addAll( - SchemaRestClient.getSchemas(SchemaType.PLAIN, schemaAnnot.anyTypeKind())); + SchemaRestClient.getSchemas(SchemaType.PLAIN, schema.anyTypeKind())); break; case DERIVED: choices.addAll( - SchemaRestClient.getSchemas(SchemaType.DERIVED, schemaAnnot.anyTypeKind())); + SchemaRestClient.getSchemas(SchemaType.DERIVED, schema.anyTypeKind())); break; case VIRTUAL: choices.addAll( - SchemaRestClient.getSchemas(SchemaType.VIRTUAL, schemaAnnot.anyTypeKind())); + SchemaRestClient.getSchemas(SchemaType.VIRTUAL, schema.anyTypeKind())); break; default: @@ -214,17 +247,29 @@ public class BeanPanel<T extends Serializable> extends Panel { new PropertyModel<>(bean.getObject(), fieldName), new ListModel(List.of(listItemType.getEnumConstants()))).hideLabel(); } else { + Triple<FieldPanel, Boolean, Optional<String>> single = + buildSinglePanel(bean.getObject(), field.getType(), field, "value"); + + setRequired(item, single.getMiddle()); + single.getRight().ifPresent(description -> setDescription(item, description)); + panel = new MultiFieldPanel.Builder<>( new PropertyModel<>(bean.getObject(), fieldName)).build( "value", fieldName, - buildSinglePanel(bean.getObject(), listItemType, fieldName, "panel")).hideLabel(); + single.getLeft()).hideLabel(); } } else if (Map.class.equals(field.getType())) { panel = new AjaxGridFieldPanel( "value", fieldName, new PropertyModel<>(bean, fieldName)).hideLabel(); } else { - panel = buildSinglePanel(bean.getObject(), field.getType(), fieldName, "value").hideLabel(); + Triple<FieldPanel, Boolean, Optional<String>> single = + buildSinglePanel(bean.getObject(), field.getType(), field, "value"); + + setRequired(item, single.getMiddle()); + single.getRight().ifPresent(description -> setDescription(item, description)); + + panel = single.getLeft().hideLabel(); } item.add(panel.setRenderBodyOnly(true)); @@ -233,33 +278,34 @@ public class BeanPanel<T extends Serializable> extends Panel { } @SuppressWarnings({ "unchecked", "rawtypes" }) - private static FieldPanel buildSinglePanel( - final Serializable bean, final Class<?> type, final String fieldName, final String id) { + private static Triple<FieldPanel, Boolean, Optional<String>> buildSinglePanel( + final Serializable bean, final Class<?> type, final Field field, final String id) { - PropertyModel model = new PropertyModel<>(bean, fieldName); + PropertyModel model = new PropertyModel<>(bean, field.getName()); - FieldPanel result; + FieldPanel panel; if (ClassUtils.isAssignable(Boolean.class, type)) { - result = new AjaxCheckBoxPanel(id, fieldName, model); + panel = new AjaxCheckBoxPanel(id, field.getName(), model); } else if (ClassUtils.isAssignable(Number.class, type)) { - result = new AjaxSpinnerFieldPanel.Builder<>().build( - id, fieldName, (Class<Number>) ClassUtils.resolvePrimitiveIfNecessary(type), model); + panel = new AjaxSpinnerFieldPanel.Builder<>().build( + id, field.getName(), (Class<Number>) ClassUtils.resolvePrimitiveIfNecessary(type), model); } else if (Date.class.equals(type)) { - result = new AjaxDateTimeFieldPanel(id, fieldName, model, + panel = new AjaxDateTimeFieldPanel(id, field.getName(), model, DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT); } else if (OffsetDateTime.class.equals(type)) { - result = new AjaxDateTimeFieldPanel(id, fieldName, new DateOps.WrappedDateModel(model), + panel = new AjaxDateTimeFieldPanel(id, field.getName(), new DateOps.WrappedDateModel(model), DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT); } else if (type.isEnum()) { - result = new AjaxDropDownChoicePanel(id, fieldName, model).setChoices(List.of(type.getEnumConstants())); + panel = new AjaxDropDownChoicePanel(id, field.getName(), model). + setChoices(List.of(type.getEnumConstants())); } else if (Duration.class.equals(type)) { - result = new AjaxTextFieldPanel(id, fieldName, new IModel<>() { + panel = new AjaxTextFieldPanel(id, field.getName(), new IModel<>() { private static final long serialVersionUID = 807008909842554829L; @Override public String getObject() { - return Optional.ofNullable(PropertyResolver.getValue(fieldName, bean)). + return Optional.ofNullable(PropertyResolver.getValue(field.getName(), bean)). map(Object::toString).orElse(null); } @@ -268,15 +314,37 @@ public class BeanPanel<T extends Serializable> extends Panel { PropertyResolverConverter prc = new PropertyResolverConverter( SyncopeWebApplication.get().getConverterLocator(), SyncopeConsoleSession.get().getLocale()); - PropertyResolver.setValue(fieldName, bean, Duration.parse(object), prc); + PropertyResolver.setValue(field.getName(), bean, Duration.parse(object), prc); } }); } else { // treat as String if nothing matched above - result = new AjaxTextFieldPanel(id, fieldName, model); + panel = new AjaxTextFieldPanel(id, field.getName(), model); + } + + boolean required = false; + Optional<String> description = Optional.empty(); + + io.swagger.v3.oas.annotations.media.Schema schema = + field.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); + if (schema != null) { + panel.setReadOnly(schema.accessMode() == Schema.AccessMode.READ_ONLY); + + required = schema.requiredMode() == Schema.RequiredMode.REQUIRED; + panel.setRequired(required); + + Optional.ofNullable(schema.example()).ifPresent(panel::setPlaceholder); + + description = Optional.ofNullable(schema.description()); + + if (panel instanceof AjaxTextFieldPanel + && panel.getModelObject() == null + && schema.defaultValue() != null) { + + ((AjaxTextFieldPanel) panel).setModelObject(schema.defaultValue()); + } } - result.hideLabel(); - return result; + return Triple.of(panel, required, description); } } diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/BeanPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/BeanPanel.html index 3d584102ab..2d3290b384 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/BeanPanel.html +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/BeanPanel.html @@ -19,8 +19,20 @@ under the License. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org"> <wicket:panel> <div wicket:id="propView"> + <wicket:fragment wicket:id="requiredFragment"> + <span wicket:id="requiredLabel"/> + </wicket:fragment> + + <wicket:fragment wicket:id="descriptionFragment"> + <span wicket:id="descriptionLabel" class="fas fa-info-circle" style="cursor: pointer"/> + </wicket:fragment> + + <wicket:fragment wicket:id="emptyFragment"> + </wicket:fragment> + <div class="form-group inforow"> - <label wicket:id="fieldName">[LABEL]</label> + <label wicket:id="fieldName">[LABEL]</label><span wicket:id="required"/> + <span wicket:id="description"/> <span wicket:id="value">[value]</span> </div> </div> diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java index 7dc9345096..513a0a1dbb 100644 --- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java +++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java @@ -20,40 +20,38 @@ package org.apache.syncope.common.lib.types; public enum EntityViolationType { - Standard(""), - InvalidAnyType("org.apache.syncope.core.persistence.validation.anytype"), - InvalidADynMemberships("org.apache.syncope.core.persistence.validation.group.adynmemberships"), - InvalidConnInstanceLocation("org.apache.syncope.core.persistence.validation.conninstance.location"), - InvalidConnPoolConf("org.apache.syncope.core.persistence.validation.conninstance.poolConf"), - InvalidMapping("org.apache.syncope.core.persistence.validation.mapping"), - InvalidKey("org.apache.syncope.core.persistence.validation.key"), - InvalidName("org.apache.syncope.core.persistence.validation.name"), - InvalidPassword("org.apache.syncope.core.persistence.validation.user.password"), - InvalidPolicy("org.apache.syncope.core.persistence.validation.policy"), - InvalidPropagationTask("org.apache.syncope.core.persistence.validation.propagationtask"), - InvalidRealm("org.apache.syncope.core.persistence.validation.realm"), - InvalidDynRealm("org.apache.syncope.core.persistence.validation.dynrealm"), - InvalidReport("org.apache.syncope.core.persistence.validation.report"), - InvalidResource("org.apache.syncope.core.persistence.validation.externalresource"), - InvalidGroupOwner("org.apache.syncope.core.persistence.validation.group.owner"), - InvalidSchemaEncrypted("org.apache.syncope.core.persistence.validation.schema.encrypted"), - InvalidSchemaEnum("org.apache.syncope.core.persistence.validation.schema.enum"), - InvalidSchemaMultivalueUnique("org.apache.syncope.core.persistence.validation.schema.multivalueUnique"), - InvalidSchedTask("org.apache.syncope.core.persistence.validation.schedtask"), - InvalidProvisioningTask("org.apache.syncope.core.persistence.validation.provisioningtask"), - InvalidPlainAttr("org.apache.syncope.core.persistence.validation.plainattr"), - InvalidUsername("org.apache.syncope.core.persistence.validation.user.username"), - InvalidValueList("org.apache.syncope.core.persistence.validation.attr.valueList"), - InvalidRemediation("org.apache.syncope.core.persistence.validation.remediation"), - MoreThanOneNonNull("org.apache.syncope.core.persistence.validation.attrvalue.moreThanOneNonNull"); + Standard, + InvalidAnyType, + InvalidADynMemberships, + InvalidConnInstanceLocation, + InvalidConnPoolConf, + InvalidMapping, + InvalidKey, + InvalidName, + InvalidPassword, + InvalidPolicy, + InvalidPropagationTask, + InvalidRealm, + InvalidDynRealm, + InvalidReport, + InvalidResource, + InvalidGroupOwner, + InvalidSchemaEncrypted, + InvalidSchemaEnum, + InvalidSchemaMultivalueUnique, + InvalidSchedTask, + InvalidProvisioningTask, + InvalidPlainAttr, + InvalidUsername, + InvalidValueList, + InvalidRemediation, + MoreThanOneNonNull; private String message; private String propertyPath; - EntityViolationType(final String message) { - this.message = message; - } + private Object invalidValue; public void setMessage(final String message) { this.message = message; @@ -71,4 +69,20 @@ public enum EntityViolationType { this.propertyPath = propertyPath; } + public void setInvalidValue(final Object invalidValue) { + this.invalidValue = invalidValue; + } + + public Object getInvalidValue() { + return invalidValue; + } + + @Override + public String toString() { + return name() + "{" + + "message=" + message + + ", propertyPath=" + propertyPath + + ", invalidValue=" + invalidValue + + '}'; + } } diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/CommandLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/CommandLogic.java index 907a343e12..02a5b25705 100644 --- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/CommandLogic.java +++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/CommandLogic.java @@ -23,8 +23,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import javax.validation.ConstraintViolation; +import javax.validation.ValidationException; +import javax.validation.Validator; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.command.CommandArgs; @@ -34,6 +38,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.IdRepoEntitlement; import org.apache.syncope.common.lib.types.IdRepoImplementationType; import org.apache.syncope.core.logic.api.Command; +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException; import org.apache.syncope.core.persistence.api.dao.ImplementationDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.entity.Implementation; @@ -45,10 +50,13 @@ public class CommandLogic extends AbstractLogic<EntityTO> { protected final ImplementationDAO implementationDAO; + protected final Validator validator; + protected final Map<String, Command<?>> perContextCommands = new ConcurrentHashMap<>(); - public CommandLogic(final ImplementationDAO implementationDAO) { + public CommandLogic(final ImplementationDAO implementationDAO, final Validator validator) { this.implementationDAO = implementationDAO; + this.validator = validator; } @PreAuthorize("hasRole('" + IdRepoEntitlement.IMPLEMENTATION_LIST + "')") @@ -110,6 +118,21 @@ public class CommandLogic extends AbstractLogic<EntityTO> { throw sce; } + if (command.getArgs() != null) { + try { + Set<ConstraintViolation<Object>> violations = validator.validate(command.getArgs()); + if (!violations.isEmpty()) { + throw new InvalidEntityException(command.getArgs().getClass().getName(), violations); + } + } catch (ValidationException e) { + LOG.error("While validating {}", command.getArgs(), e); + + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidValues); + sce.getElements().add(e.getMessage()); + throw sce; + } + } + try { return runnable.run(command.getArgs()); } catch (Exception e) { diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java index 9e004bd247..9904587bf4 100644 --- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java +++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java @@ -20,6 +20,7 @@ package org.apache.syncope.core.logic; import java.util.ArrayList; import java.util.List; +import javax.validation.Validator; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; @@ -267,8 +268,8 @@ public class IdRepoLogicContext { @ConditionalOnMissingBean @Bean - public CommandLogic commandLogic(final ImplementationDAO implementationDAO) { - return new CommandLogic(implementationDAO); + public CommandLogic commandLogic(final ImplementationDAO implementationDAO, final Validator validator) { + return new CommandLogic(implementationDAO, validator); } @ConditionalOnMissingBean diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/job/MacroRunJobDelegate.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/job/MacroRunJobDelegate.java index 9dab5397aa..781edbd3a2 100644 --- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/job/MacroRunJobDelegate.java +++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/job/MacroRunJobDelegate.java @@ -19,7 +19,10 @@ package org.apache.syncope.core.logic.job; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; import org.apache.syncope.common.lib.command.CommandArgs; import org.apache.syncope.core.logic.api.Command; import org.apache.syncope.core.persistence.api.dao.ImplementationDAO; @@ -38,6 +41,9 @@ public class MacroRunJobDelegate extends AbstractSchedTaskJobDelegate<MacroTask> @Autowired protected ImplementationDAO implementationDAO; + @Autowired + protected Validator validator; + protected final Map<String, Command<?>> perContextCommands = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") @@ -67,12 +73,19 @@ public class MacroRunJobDelegate extends AbstractSchedTaskJobDelegate<MacroTask> output.append(command).append(' ').append(args); } else { try { + if (task.getCommandArgs().get(i) != null) { + Set<ConstraintViolation<Object>> violations = validator.validate(task.getCommandArgs().get(i)); + if (!violations.isEmpty()) { + LOG.error("Errors while validating {}: {}", task.getCommandArgs().get(i), violations); + throw new IllegalArgumentException(task.getCommandArgs().get(i).getClass().getName()); + } + } + output.append(runnable.run(task.getCommandArgs().get(i))); } catch (Exception e) { if (task.isContinueOnError()) { output.append("Continuing on error: <").append(e.getMessage()).append('>'); - LOG.error("While running {} with args {}, continuing on error", - command.getKey(), args, e); + LOG.error("While running {} with args {}, continuing on error", command.getKey(), args, e); } else { throw new RuntimeException("While running " + command.getKey(), e); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java index 9e70892098..8870b4ea36 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.validation.ConstraintViolation; import javax.validation.ValidationException; import org.apache.commons.lang3.StringUtils; @@ -76,28 +77,22 @@ public class InvalidEntityException extends ValidationException { this.entityClassSimpleName = entityClassSimpleName; violations.forEach(violation -> { - int firstComma = violation.getMessageTemplate().indexOf(';'); - - final String key = violation.getMessageTemplate().substring( - 0, firstComma > 0 ? firstComma : violation.getMessageTemplate().length()); - - final String message = violation.getMessageTemplate().substring(firstComma > 0 ? firstComma + 1 : 0); + String key = StringUtils.substringBefore(violation.getMessageTemplate(), ";").trim(); + String message = StringUtils.substringAfter(violation.getMessageTemplate(), ";").trim(); EntityViolationType entityViolationType; try { - entityViolationType = EntityViolationType.valueOf(key.trim()); + entityViolationType = EntityViolationType.valueOf(key); } catch (IllegalArgumentException e) { entityViolationType = EntityViolationType.Standard; } - - entityViolationType.setMessage(message.trim()); - + entityViolationType.setMessage(message); entityViolationType.setPropertyPath(violation.getPropertyPath().toString()); + entityViolationType.setInvalidValue(violation.getInvalidValue()); if (!this.violations.containsKey(violation.getLeafBean().getClass())) { this.violations.put(violation.getLeafBean().getClass(), EnumSet.noneOf(EntityViolationType.class)); } - this.violations.get(violation.getLeafBean().getClass()).add(entityViolationType); }); } @@ -116,12 +111,8 @@ public class InvalidEntityException extends ValidationException { @Override public String getMessage() { - StringBuilder sb = new StringBuilder(); - - violations.forEach( - (key, value) -> sb.append(key.getSimpleName()).append(' ').append(value.toString()).append(", ")); - sb.delete(sb.lastIndexOf(", "), sb.length()); - - return sb.toString(); + return violations.entrySet().stream(). + map(entry -> entry.getKey().getSimpleName() + " " + entry.getValue().toString()). + collect(Collectors.joining(",")); } } diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommandArgs.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommandArgs.java index 0aa7fe4e5e..3f83a3949c 100644 --- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommandArgs.java +++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommandArgs.java @@ -18,17 +18,26 @@ */ package org.apache.syncope.fit.core.reference; +import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.constraints.NotEmpty; import org.apache.syncope.common.lib.command.CommandArgs; public class TestCommandArgs extends CommandArgs { private static final long serialVersionUID = 1408260716514938521L; - private String parentRealm = "/even/two"; + @NotEmpty + @Schema(description = "parent realm", example = "/even/two", defaultValue = "/", + requiredMode = Schema.RequiredMode.REQUIRED) + private String parentRealm = "/"; - private String realmName = "realm123"; + @NotEmpty + @Schema(description = "new realm name", example = "realm123", requiredMode = Schema.RequiredMode.REQUIRED) + private String realmName; - private String printerName = "printer123"; + @NotEmpty + @Schema(description = "printer name", example = "printer123", requiredMode = Schema.RequiredMode.REQUIRED) + private String printerName; public String getParentRealm() { return parentRealm; diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java index cc3d1dd7e8..cb2cfc72bd 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import javax.ws.rs.core.Response; import org.apache.syncope.common.lib.SyncopeClientException; @@ -30,6 +31,7 @@ import org.apache.syncope.common.lib.command.CommandTO; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.ImplementationTO; import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.IdRepoImplementationType; import org.apache.syncope.common.lib.types.ImplementationEngine; import org.apache.syncope.common.rest.api.RESTHeaders; @@ -76,11 +78,26 @@ public class CommandITCase extends AbstractITCase { assertTrue(command.getArgs() instanceof TestCommandArgs); } + @Test + public void argsValidationFailure() { + CommandTO command = COMMAND_SERVICE.search(new CommandQuery.Builder(). + keyword(TestCommand.class.getSimpleName()).page(1).size(1).build()).getResult().get(0); + + try { + COMMAND_SERVICE.run(command); + fail(); + } catch (SyncopeClientException e) { + assertEquals(ClientExceptionType.InvalidValues, e.getType()); + } + } + @Test public void runCommand() { CommandTO command = COMMAND_SERVICE.search( new CommandQuery.Builder().page(1).size(1).build()).getResult().get(0); TestCommandArgs args = ((TestCommandArgs) command.getArgs()); + args.setParentRealm("/even/two"); + args.setRealmName("realm123"); args.setPrinterName("printer124"); CommandOutput output = COMMAND_SERVICE.run(command); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java index aebedba892..1391335b49 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java @@ -57,6 +57,14 @@ public class MacroITCase extends AbstractITCase { private static String MACRO_TASK_KEY; + private static final TestCommandArgs TCA = new TestCommandArgs(); + + static { + TCA.setParentRealm("/odd"); + TCA.setRealmName("macro"); + TCA.setPrinterName("aprinter112"); + } + @BeforeAll public static void testCommandsSetup() throws Exception { CommandITCase.testCommandSetup(); @@ -87,8 +95,7 @@ public class MacroITCase extends AbstractITCase { task.setActive(true); task.setRealm("/odd"); task.getCommands().add(new CommandTO.Builder("GroovyCommand").build()); - task.getCommands().add(new CommandTO.Builder(TestCommand.class.getSimpleName()). - args(new TestCommandArgs()).build()); + task.getCommands().add(new CommandTO.Builder(TestCommand.class.getSimpleName()).args(TCA).build()); Response response = TASK_SERVICE.create(TaskType.MACRO, task); task = getObject(response.getLocation(), TaskService.class, MacroTaskTO.class); @@ -124,10 +131,9 @@ public class MacroITCase extends AbstractITCase { ExecTO exec = TASK_SERVICE.read(TaskType.MACRO, MACRO_TASK_KEY, true).getExecutions().get(preExecs); assertEquals(ExecStatus.SUCCESS.name(), exec.getStatus()); - TestCommandArgs args = new TestCommandArgs(); - AnyObjectTO printer = ANY_OBJECT_SERVICE.read(args.getPrinterName()); + AnyObjectTO printer = ANY_OBJECT_SERVICE.read(TCA.getPrinterName()); assertNotNull(printer); - assertEquals(args.getParentRealm() + "/" + args.getRealmName(), printer.getRealm()); + assertEquals(TCA.getParentRealm() + "/" + TCA.getRealmName(), printer.getRealm()); assertFalse(REALM_SERVICE.list(printer.getRealm()).isEmpty()); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java index 36ecb8a460..b0e52ef103 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import com.fasterxml.jackson.core.JsonProcessingException; import javax.ws.rs.core.Response; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.Attr; @@ -62,7 +63,7 @@ import org.springframework.jdbc.core.JdbcTemplate; public class MembershipITCase extends AbstractITCase { @Test - public void misc() { + public void misc() throws JsonProcessingException { UserCR userCR = UserITCase.getUniqueSample("m...@apache.org"); userCR.setRealm("/even/two"); userCR.getPlainAttrs().add(new Attr.Builder("aLong").value("1976").build()); diff --git a/pom.xml b/pom.xml index e92241a2d9..3027aad8ce 100644 --- a/pom.xml +++ b/pom.xml @@ -1923,7 +1923,7 @@ under the License. <dependency> <groupId>com.puppycrawl.tools</groupId> <artifactId>checkstyle</artifactId> - <version>10.4</version> + <version>10.5.0</version> </dependency> </dependencies> <configuration>