Why does method validation not work with custom Hibernate Validator annotations? - hibernate-validator

I'm experimenting with Hibernate Validator's method validation.
I've written the simplest EJB I can imagine and annotating the constraints on it.
However, it won't work with my custom annotations.
public String checkString(#NotNull #NotBlank #Characters(invalidCharacters = { '1' }) String string);
The last annotation checks that a String contains certain characters of a character set (with a default) and is a custom constraint. This works in a normal Validator environment.
However in my test, only the standard annotations work, my custom annotations don't.
this.parameterValidation.checkString(null); // Throws Exception
this.parameterValidation.checkString(""); // Throws Exception
this.parameterValidation.checkString("123"); // Does NOT throw Exception - why?
I've also tested another custom annotation, and that remains inactive as well.
What am I doing wrong?
Here's the code of the custom annotation:
/**
* Checks if a String contains only characters from the given character set. Note that this sets a parameter
* {#code positions} with a comma-separated list of the positions of invalid characters (based on 1, not 0!).
*/
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#Constraint(validatedBy = CharactersCheck.class)
public #interface Characters {
/**
* The I18N key of the error message.
*/
public static final String I18N_KEY = "ipi.msg.validation.characters";
/**
* Error message.
*/
String message() default LEFT_CURLY_BRACKET + I18N_KEY + RIGHT_CURLY_BRACKET;
/**
* The associated violation groups.
*/
Class<?>[] groups() default {};
/**
* The payload.
*/
Class<? extends Payload>[] payload() default {};
/**
* The character set to which the text must conform.
*/
CharacterSet characterSet() default CharacterSet.ISO_8859_15;
/**
* Additional characters which must not be found in the text.
*/
char[] invalidCharacters() default {};
/**
* If this is {#code true}, carriage returns and line feeds are allowed in the text, making it a multi-line text.
*/
boolean carriageReturnAllowed() default false;
/**
* Defines several {#link Characters} annotations on the same element.
*/
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#interface List {
/**
* The {#link Characters} annotations.
*/
Characters[] value();
}
}
Here's the implementation:
public class CharactersCheck implements ConstraintValidator<Characters, CharSequence>, MessageAttributeModifier {
/**
* The character set to check.
*/
private CharacterSet characterSet;
/**
* Additional invalid characters.
*/
private char[] invalidCharacters;
/**
* If this is {#code true}, carriage returns and line feeds are allowed in the text, making it a multi-line text.
*/
private boolean carriageReturnAllowed;
private SortedSet<Integer> invalidCharacterPositions;
/**
* {#inheritDoc}
*/
#Override
public void initialize(final Characters constraintAnnotation) {
this.characterSet = constraintAnnotation.characterSet();
this.carriageReturnAllowed = constraintAnnotation.carriageReturnAllowed();
if (this.carriageReturnAllowed) {
this.invalidCharacters = constraintAnnotation.invalidCharacters();
} else {
final int invalidCharactersLength = constraintAnnotation.invalidCharacters().length;
this.invalidCharacters = Arrays.copyOf(constraintAnnotation.invalidCharacters(), invalidCharactersLength + 2);
this.invalidCharacters[invalidCharactersLength] = '\r';
this.invalidCharacters[invalidCharactersLength + 1] = '\n';
}
this.invalidCharacterPositions = new TreeSet<>();
}
/**
* {#inheritDoc}
*/
#Override
public boolean isValid(final CharSequence value, final ConstraintValidatorContext context) {
if (value == null) {
return true;
} else {
setInvalidCharacterPositions(value);
return this.invalidCharacterPositions.isEmpty();
}
}
private void setInvalidCharacterPositions(final CharSequence value) {
this.invalidCharacterPositions = CharacterChecker.checkCharacters(String.valueOf(value), this.characterSet,
this.invalidCharacters);
}
/**
* {#inheritDoc}
*/
#Override
public void modifyAttributes(final Map<String, Object> attributes, final Context context) {
setInvalidCharacterPositions((CharSequence) context.getValidatedValue());
if (!this.invalidCharacterPositions.isEmpty()) {
attributes.put(MessageVariables.POSITIONS, StringUtils.join(this.invalidCharacterPositions, ", "));
}
}
}
Note that I created a custom message interpolator which lets checks add message variables to the given Map. For this I create a new instance of the check within the message interpolator and call it using this interface:
/**
* Modifies attributes within a validation message.
*/
public interface MessageAttributeModifier {
/**
* Modifies the attributes within the validation message.
* #param attributes The existing attributes, which can be modified; the attributes already contain the values from
* the annotation and additionally also {#link MessageVariables#VALIDATED_VALUE}
* #param context The validation context
*/
public void modifyAttributes(Map<String, Object> attributes, final Context context);
}

Related

How to set validation order in symfony, field validation depends on another field?

For example, I have an entity with two fields:
/**
* #Assert\Range(min=1, max=self::SPEND_MAX)
*/
public ?int $spendMax = null;
/**
* #Assert\NotBlank()
* #Assert\Length(max=255)
*/
public string $name;
How I can set for spendMax field, that first of all I need to validate if field name is not null, if it's true, that I can start validation Range of spendMax.
If name is null, then validation of spendMax is false.
You can make your own custom method to do the validation and arrange for it to be called before persisting or updating the entity.
Something along these lines:
abstract class ValidatableEntity
{
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function validate(): void
{
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator()
;
$violations = $validator->validate($this);
if (0 !== $violations->count()) {
throw new ValidationFailedException($this, $violations);
}
}
/**
* Intended to be called from the prePersist Event from EntityPersistenceEventSubscriber service
* when this object is to be persisted.
* The method is intended to be overridden in derived classes and does nothing here.
* If the validation in the derived class is unsuccessful, the method should throw
* a ValidationFailedException.
* Note that since the object has not yet been persisted, its ID field will not be defined in this method.
*/
public function prePersistValidation(EntityManager $entityManager): void
{
return;
}
/**
* Intended to be called from the preUpdate Event from EntityPersistenceEventSubscriber service
* when this object is to be updated.
* The method is intended to be overridden in derived classes and does nothing here.
* If the validation in the derived class is unsuccessful, the method should throw
* a ValidationFailedException.
*/
public function preUpdateValidation(PreUpdateEventArgs $args): void
{
// How to get an entity manager here:
// $entityManager = $args->getObjectManager();
return;
}
To use it, derive your entity class from this one and override the preUpdateValidation and prePersistValidation methods.

Handling imporper data during deserialization when using Symfony Serializer Component

I am new to the Symfony serializer component. I am trying to properly deserialize a JSON body to the following DTO:
class PostDTO
{
/** #var string */
private $name;
/**
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
}
The controller method as follows:
/**
* #Route (path="", methods={"POST"}, name="new_post")
* #param Request $request
* #return Response
*/
public function create(Request $request): Response
{
$model = $this->serializer->deserialize($request->getContent(), PostDTO::class, 'json');
// call the service with the model
return new JsonResponse();
}
My problem is that I wanted to handle business-validation after the body was deserialized. However, if i specify an invalid value for the name, such as false or [], the deserialization will fail with an exception: Symfony\Component\Serializer\Exception\NotNormalizableValueException: "The type of the "name" attribute for class "App\Service\PostDTO" must be one of "string" ("array" given)..
I do understand that it is because I intentionally set "name": []. However, I was looking for a way to set the fields to a default value or even perform some validation pre-deserialization.
I have found the proper way to handle this. That exception was thrown because the serializer was not able to create the PostDTO class using the invalid payload I have provided.
To handle this, I have created my custom denormalizer which kicks in only for this particular class. To do this, I have implemented the DenormalizerInterface like so:
use App\Service\PostDTO;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class PostDTODeserializer implements DenormalizerInterface
{
/** #var ObjectNormalizer */
private $normalizer;
/**
* PostDTODeserializer constructor.
* #param ObjectNormalizer $normalizer
*/
public function __construct(ObjectNormalizer $normalizer)
{
$this->normalizer = $normalizer;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
return $type === PostDTO::class;
}
/**
* #param mixed $data
* #param string $type
* #param string|null $format
* #return array|bool|object
* #throws ExceptionInterface
*/
public function supportsDenormalization($data, string $type, string $format = null)
{
// validate the array which will be normalized (you should write your validator and inject it through the constructor)
if (!is_string($data['name'])) {
// normally you would throw an exception and leverage the `ErrorController` functionality
// do something
}
// convert the array to the object
return $this->normalizer->denormalize($data, $type, $format);
}
}
If you want to access the context array, you can implement the DenormalizerAwareInterface. Normally, you would create your custom validation and inject it into this denormalizer and validate the $data array.
Please not that I have injected the ObjectNormalizer here so that when the data successfully passed the validation, I can still construct the PostDTO using the $data.
PS: in my case, the autowiring has automatically registered my custom denormalizer. If yours is not autowired automatically, go to services.yaml and add the following lines:
App\Serializer\PostDTODeserializer:
tags: ['serializer.normalizer']
(I have tagged the implementation with serializer.normalizer so as it is recognized during the deserialization pipeline)

Mass Assignment: Insecure Binder Configuration (Fortify Error) after adding #JsonProperty Annotation

I am getting Mass Assignment: Insecure Binder Configuration in fortify analysis.
Here is the AuthorisationController.class
#Controller
public class AuthorisationController {
#RequestMapping(value = "/authorisation_request", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<AuthorisationRequest> createAuthorisation(HttpServletRequest request,
#RequestBody AuthorisationRequestInfo createAuthorisation) {
//processing code
}
}
Here is the AuthorisationRequestInfo.class on which the http request params will be mapped.
import com.fasterxml.jackson.annotation.JsonProperty;
public class OrderAuthorisationRequestInfo {
private String hashValue;
private String expiryDateTime;
private Integer initiatingRolePlayerId;
#JsonProperty("feedbackURI")
private String feedbackUri;
/**
* Gets the hash value.
*
* #return the hash value
*/
public String getHashValue() {
return hashValue;
}
/**
* Sets the hash value.
*
* #param hashValue the new hash value
*/
public void setHashValue(String hashValue) {
this.hashValue = hashValue;
}
/**
* Gets the expiry date time.
*
* #return the expiry date time
*/
public String getExpiryDateTime() {
return expiryDateTime;
}
/**
* Sets the expiry date time.
*
* #param expiryDateTime the new expiry date time
*/
public void setExpiryDateTime(String expiryDateTime) {
this.expiryDateTime = expiryDateTime;
}
/**
* Gets the initiating role player id.
*
* #return the initiating role player id
*/
public Integer getInitiatingRolePlayerId() {
return initiatingRolePlayerId;
}
/**
* Sets the initiating role player id.
*
* #param initiatingRolePlayerId the new initiating role player id
*/
public void setInitiatingRolePlayerId(Integer initiatingRolePlayerId) {
this.initiatingRolePlayerId = initiatingRolePlayerId;
}
/**
* Gets the feedback URI.
*
* #return the feedback URI
*/
public String getFeedbackUri() {
return feedbackUri;
}
/**
* Sets the feedback URI.
*
* #param feedbackUri the new feedback URI
*/
public void setFeedbackUri(String feedbackUri) {
this.feedbackUri = feedbackUri;
}
}
The interesting thing is that I only started getting this error after adding the #JsonProperty("feedbackURI") annotation on the feedbackUri column.
#InitBinder was not being used before and there was no fortify error and all the parameters in the request are mandatory.
All other APIs are fine and do not report any fortify issues. Only this api and another one in which the #JsonProperty was added have started showing this error.
Any help would be appreciated.
You can use #JsonIgnoreProperties in your case:
#JsonIgnoreProperties(ignoreUnknown = true)
public class OrderAuthorisationRequestInfo {

symonfy/doctrine, get associated entity return null , but return actual data if a call to `dump()` is added

This is one is a bit weird
I'm using symfony3/php7
I have the following ProUser entity linked to a Organization entity, used to identity pro account, (important part is the "isEnabled" method), when I try to login with a ProUser that has a linked Organization (they all have, but I made triple sure to choose one that had in database), I got an error that the organization is null, but if i had a dump method to debug, then the organization is correctly retrieved from database by doctrine...
/**
* Represent a professional owner (i.e a theater owner etc.)
*
* #ORM\Entity
* #ORM\Table(name="pro_user")
*/
class ProUser implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\Column(name="id", type="guid")
* #ORM\Id
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Organization", cascade={"persist"}, mappedBy="legalRepresentative")
*/
private $organization;
public function getOrganization()
{
return $this->organization;
}
public function setOrganization(Organization $organization)
{
$this->organization = $organization;
return $this;
}
/**
* Note: needed to implement the UserInterface
*/
public function getUsername()
{
return $this->email;
}
// for AdvancedUserInterface
public function isEnabled(): bool
{
$organization = $this->getOrganization();
// when this line is not present,
// it throws an exception that $organization is null,
// no problem when this line is present
dump($organization);
return $organization->isValidated();
}
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isCredentialsNonExpired()
{
return true;
}
}
The stacktrace :
Symfony\Component\Debug\Exception\FatalThrowableError:
Call to a member function isValidated() on null
at src/AppBundle/Entity/ProUser.php:151
at AppBundle\Entity\ProUser->isEnabled()
(vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php:277)
at Symfony\Component\Security\Core\Authentication\Token\AbstractToken->hasUserChanged(object(ProUser))
(vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php:101)
at Symfony\Component\Security\Core\Authentication\Token\AbstractToken->setUser(object(ProUser))
(vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ContextListener.php:176)
at Symfony\Component\Security\Http\Firewall\ContextListener->refreshUser(object(RememberMeToken))
(vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ContextListener.php:109)
at Symfony\Component\Security\Http\Firewall\ContextListener->handle(object(GetResponseEvent))
(vendor/symfony/symfony/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php:46)
at Symfony\Bundle\SecurityBundle\Debug\WrappedListener->handle(object(GetResponseEvent))
(vendor/symfony/symfony/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php:35)
at Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener->handleRequest(object(GetResponseEvent), object(RewindableGenerator))
(vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall.php:56)
at Symfony\Component\Security\Http\Firewall->onKernelRequest(object(GetResponseEvent))
(vendor/symfony/symfony/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php:48)
Is it due to the code happening in the Security Component, and the entity was unserialized instead of being retrieved by doctrine, so that getOrganization() does not yet return a doctrine proxy ?
This is because of Doctrine's lazy loading of relations (it basically only knows the primary ids of the connected entities untill one or more of them are called, like with dump()).
You can add the fetch attribute to your mapping, where LAZY is default, you can set this to EAGER.

Using Reflection To Instantiate 'Builder Pattern' (Joshua Bloch)

When attempting to use Joshua Bloch's "Builder Pattern" [Item 2 in Effective Java Second Edition] with reflection [object = constructors[index].newInstance(constructorParameterValues);] the following exception occurs:
java.lang.IllegalAccessException: Class info.soaj.core.util.SjUtilReflection can not access a member of class info.soaj.core.attribute.SjAttributesForThrowable with modifiers "private"
Note: This has been resolved. The accessible (private) constructor was being discarded and a non-accessible (override = false) was being attempted. Bottom Line: Programmer Error
An example Builder Class follows:
package info.soaj.core.attribute;
import info.soaj.core.attribute.internal.SjAttributesForStronglyTypedWrappers;
import info.soaj.core.internal.string.SjPopulatedClassName;
import info.soaj.core.internal.string.SjPopulatedMethodName;
import info.soaj.core.util.internal.SjUtilThrowable;
import java.io.Serializable;
/**
* <p>
* The "Builder" pattern as documented by Joshua Bloch ("Effective Java" -
* Second Edition) is utilized to handle the variable number of required and
* optional parameters.
* </p>
*
* <p style="font-family:Verdana; font-size:10px; font-style:italic"> Copyright
* (c) 2006 - 2008 by Global Technology Consulting Group, Inc. at <a
* href="http://gtcGroup.com">gtcGroup.com </a>. </p>
*
* #author MarvinToll#gtcGroup.com
* #since v. 1.0
*/
public class SjAttributesExample implements Serializable {
/** UID */
private static final long serialVersionUID = 1L;
/** The name of class throwing the exception. */
protected final SjPopulatedClassName classname;
/** The name of method throwing the exception. */
protected final SjPopulatedMethodName methodname;
/**
* Suppresses logging; default is <code>false</code>.
*/
protected final boolean suppressLoggingOnly;
/**
* Constructor - private
*
* #param builderThrowable
*/
private SjAttributesExample(final BuilderThrowable builderThrowable) {
this.classname = builderThrowable.classname;
this.methodname = builderThrowable.methodname;
this.suppressLoggingOnly = builderThrowable.suppressLoggingOnly;
}
/**
* This static member immutable class is used to implement the builder
* pattern.
*
* #author MarvinToll#gtcGroup.com
* #since v. 1.0
*/
public static class BuilderThrowable {
/** Class name. */
private static final String CLASS_NAME = BuilderThrowable.class
.getName();
// Required attributes.
/** The name of class throwing the exception. */
protected final SjPopulatedClassName classname;
/** The name of method throwing the exception. */
protected final SjPopulatedMethodName methodname;
// Optional attributes.
/** Prevents action from occurring. Default is false. */
protected boolean suppressLoggingOnly = false;
/**
* Constructor
*
* #param classname
* #param methodname
*/
public BuilderThrowable(final String classname, final String methodname) {
super();
final String Method_Name = "BuilderThrowable";
// What happens when handling an exception throws an exception?
try {
this.classname = new SjPopulatedClassName(classname,
new SjAttributesForStronglyTypedWrappers(CLASS_NAME,
Method_Name));
this.methodname = new SjPopulatedMethodName(methodname,
new SjAttributesForStronglyTypedWrappers(CLASS_NAME,
Method_Name));
} catch (final RuntimeException e) {
// Log the contextual details.
SjUtilThrowable.logExceptionOccuredWhileThrowingException(
CLASS_NAME, Method_Name, e);
throw e;
}
return;
}
/**
* This method sets a flag to suppress logging.
*
* #param isLoggingSuppressed
* #return BuilderThrowable
*/
public BuilderThrowable suppressLoggingOnly(
final boolean isLoggingSuppressed) {
this.suppressLoggingOnly = isLoggingSuppressed;
return this;
}
/**
* This method is used for instantiating this class.
*
* #return SjAttributesForThrowable
*/
#SuppressWarnings("synthetic-access")
public SjAttributesExample build() {
return new SjAttributesExample(this);
}
}
/**
* This method returns an attribute.
*
* #return String - Returns the <code>classname</code> attribute.
*/
public String getClassname() {
return this.classname.getString();
}
/**
* This method returns an attribute.
*
* #return String - Returns the <code>methodname</code> attribute.
*/
public String getMethodname() {
return this.methodname.getString();
}
/**
* This method returns an attribute.
*
* #return boolean - Returns the <code>suppressLoggingOnly</code> attribute.
*/
public boolean isLoggingSuppressed() {
return this.suppressLoggingOnly;
}
}
Note: This has been resolved. The accessible (private) constructor was being discarded and a non-accessible (override = false) was being attempted. Bottom Line: Programmer Error

Resources