Our stack is PHP 8.1, Symfony 6.1 and EasyAdmin 4.4, the context of the error is we are trying to edit an entity record through normal EA CRUD edit, the error when I try to edit the record is:
15:41:19.128 critical Uncaught PHP Exception ErrorException: "User Notice: Undefined property: Proxies\__CG__\App\Entity\Sys\Auth\RoleClass::$ea_form_panel_01GKPKZ6CGWJ4FX3Q4J5C6C961" at /opt/app/var/cache/dev/doctrine/orm/Proxies/__CG__AppEntitySysAuthRoleClass.php line 101
request Show context Hide trace
{▼
/opt/app/var/cache/dev/doctrine/orm/Proxies/__CG__AppEntitySysAuthRoleClass.php:101 {▶}
/opt/app/vendor/symfony/property-access/PropertyAccessor.php:426 {▶}
/opt/app/vendor/symfony/property-access/PropertyAccessor.php:115 {▶}
/opt/app/vendor/easycorp/easyadmin-bundle/src/Field/Configurator/CommonPreConfigurator.php:47 {▶}
/opt/app/vendor/easycorp/easyadmin-bundle/src/Factory/FieldFactory.php:100 {▶}
/opt/app/vendor/easycorp/easyadmin-bundle/src/Factory/EntityFactory.php:43 {▶}
/opt/app/vendor/easycorp/easyadmin-bundle/src/Controller/AbstractCrudController.php:218 {▶}
/opt/app/vendor/symfony/http-kernel/HttpKernel.php:163 {▶}
/opt/app/vendor/symfony/http-kernel/HttpKernel.php:75 {▶}
/opt/app/vendor/symfony/http-kernel/Kernel.php:202 {▶}
/opt/app/public/index.php:30 {▶}
}
What appears to be happening is the following:
The Fields returned from configureFields() (in edit page mode) include Panel Fields
The entity instance is a Proxy and not a 'real class'. I can't replicate this anywhere else, in all other cases (i.e. all edit views on all other entities I see a 'real class' and not a Proxy), in fact this error only occurs on specific instances of this entity type, most are fine, just 1 record causes this issue
Since the entity instance is a Doctrine Proxy, the PropertyAccessor.php works differently because $this->getReadInfo($class, $property); treats Proxies differently as they have magic __get functions (it seems to assume that the property exists in ReflectionExtractor.getReadInfo on line 293 (I'm clearly missing something):
if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
}
I am completely stumped as to why $context->getEntity()->getInstance() in AbstractCrudController::edit is returning a Doctrine Proxy in this very specific case (I've never seen this happen before). Tracing the code it feels that this should never be a Proxy (or you can't use Fields that don't exist on the Entity like Panels and Tabs).
Related
I try to improve myself with .NET Web API now and I am trying to return a custom error in Swagger. But when returning this custom error, I can see the error is on which line. How can I do to prevent this?
public async Task<BookCreateDTO> CreateBook(BookCreateDTO bookCreateDto)
{
if (await _context.Books.AnyAsync(x => x.Name == bookCreateDto.Name))
{
throw new BookExistException("Book already exist");
}
var book= _mapper.Map<Book>(bookCreateDto);
_context.Books.Add(book);
await _context.SaveChangesAsync();
return book;
}
What should I do to see only this exception message in the Swagger response?
Thank you for your help.
Exceptions should be exceptional: Don't throw exceptions for non-exceptional errors.
I don't recommend specifying your web-service's response DTO type in the C# action method return type because it limits your expressiveness (as you're discovering).
Instead use IActionResult or ActionResult<T> to document the default (i.e. HTTP 2xx) response type and then list error DTO types in [ProducesResponseType] attributes with their corresponding HTTP status codes.
This also means that each response status code should only be associated with a single DTO type.
While Swagger is not expressive enough to allow you to say "if the response status is HTTP 200 then the response body/DTO is one-of DtoFoo, DtoBar, DtoQux", in-practice a well-designed web-service API should not exhibit that kind of response DTO polymorphism.
And if it didn't, how else is a client supposed to know what the type is just from the HTTP headers? (Well, you could put the full DTO type-name in a custom HTTP response header, but that introduces other problems...)
For error conditions, add the errors to ModelState (with the Key, if possible) and let ASP.NET Core handle the rest for you with ProblemDetails.
If you do throw an exception, then ASP.NET Core can be configured to automatically render it as a ProblemDetails - or it can show the DeveloperExceptionPage - or something else entirely.
I note that a good reason to not throw an exception inside a Controller for non-exceptional exceptions is because your logging framework may choose to log more details about unhandled exceptions in ASP.NET Core's pipeline, which would result in useless extraneous entries in your logs that make it harder to find "real" exceptions that you need to fix.
Document the DTOs used, and their corresponding HTTP status codes, with [ProducesResponseType]: this is very useful when using Swagger/NSwag to generate online documentation and client libraries.
Also: do not use EF entity types as DTOs or ViewModels.
Reason 1: When the response (with EF entity objects) is serialized, entities with lazy-loaded properties will cause your entire database object-graph to be serialized (because the JSON serializer will traverse every property of every object).
Reason 2: Security! If you directly accept an EF entity as an input request body DTO or HTML form model then users/visitors can set properties arbitrarily, e.g. POST /users with { accessLevel: 'superAdmin' }, for example. While you can exclude or restrict which properties of an object can be set by a request it just adds to your project's maintenance workload (as it's another non-local, manually-written, list or definition in your program you need to ensure is kept in-sync with everything else.
Reason 3: Self-documenting intent: an entity-type is for in-proc state, not as a communications contract.
Reason 4: the members of an entity-type are never exactly what you'll want to expose in a DTO.
For example, your User entity will have a Byte[] PasswordHash and Byte[] PasswordSalt properties (I hope...), and obviously those two properties must never be exposed; but in a User DTO for editing a user you might want different members, like NewPassword and ConfirmPassword - which don't map to DB columns at all.
Reason 5: On a related note to Reason 4, using Entity classes as DTOs automatically binds the exact design of your web-service API to your database model.
Supposing that one day you absolutely need to make changes to your database design: perhaps someone told you the business requirements changed; that's normal and happens all the time.
Supposing the DB design change was from allowing only 1 address per customer (because the street addresses were being stored in the same table as customers) to allowing customers to have many addresses (i.e. the street-address columns are moved to a different table)...
...so you make the DB changes, run the migration script, and deploy to production - but suddenly all of your web-service clients stop working because they all assumed your Customer object had inline Street address fields but now they're missing (because your Customer EF entity types' don't have street-address columns anymore, that's over in the CustomerAddress entity class).
If you had been using a dedicated DTO type specifically for Customer objects then during the process of updating the design of the application you would have noticed builds breaking sooner (rather than inevitably later!) due to C# compile-time type-checking in your DTO-to-Entity (and Entity-to-DTO) mapping code - that's a benefit right there.
But the main benefit is that it allows you to completely abstract-away your underlying database design - and so, in our example, if you have remote clients that depend on Customer address information being inline then your Customer DTO can still emulate the older design by inlining the first Customer Address into the original Customer DTO when it renders its JSON/XML/Protobuf response to the remote client. That saves time, trouble, effort, money, stress, complaints, firings, unnecessary beatings, grievous bodily harm and a scheduled dental hygienist's appointment.
Anyway, I've modified your posted code to follow the guidance above:
I added [ProducesResponseType] attributes.
I appreciate it is redundant to specify the default response type BookCreateDTO twice (in [ProducesResponseType] as well as ActionResult<BookCreateDTO> - you should be able to remove either one of those without affecting Swagger output.
I added an explicit [FromBody], just to be safe.
If the "book-name is unused" check fails, it returns the model validation message in ASP.NET's stock BadRequest response, which is rendered as an IETF RFC 7807 response, aka ProblemDetails instead of throwing an exception and then hoping that you configured your ASP.NET Core pipeline (in Configure()) to handle it as a ProblemDetails instead of, say, invoking a debugger or using DeveloperExceptionPage.
Note that in the case of a name conflict we want to return HTTP 409 Conflict and not HTTP 400 Bad Request, so the conflictResult.StatusCode = 409; is overwritten.
The final response is generated from a new BookCreateDTO instance via AutoMapper and Ok() instead of serializing your Book entity object.
[ProducesResponseType(typeof(BookCreateDTO), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
public async Task< ActionResult<BookCreateDTO> > CreateBook( [FromBody] BookCreateDTO bookCreateDto )
{
// Does a book with the same name exist? If so, then return HTTP 409 Conflict.
if( await _context.Books.AnyAsync(x => x.Name == bookCreateDto.Name) )
{
this.ModelState.Add( nameof(BookCreateDTO.Name), "Book already exists" );
BadRequestObjectResult conflictResult = this.BadRequest( this.ModelState );
// `BadRequestObjectResult` is HTTP 400 by default, change it to HTTP 409:
conflictResult.StatusCode = 409;
return conflictResult;
}
Book addedBook;
{
addedBook = this.mapper.Map<Book>( bookCreateDto );
_ = this.context.Books.Add( book );
_ = await this.context.SaveChangesAsync();
}
BookCreateDTO responseDto = this.mapper.Map<BookCreateDTO >( addedBook );
return this.Ok( responseDto );
}
Following this
http://symfony.com/doc/current/security/custom_authentication_provider.html
Results in
Service "security.authentication.provider.wsse.wsse_secured": Cannot replace arguments if none have been configured yet.
I cannot find anything about this error anywhere. This uses the doc's WSSE code and it fails.
This repo shows it failing https://github.com/jakenoble/wsse_test
I want to get it working eventually with the FOS User Bundle. But I cannot get it to work with a basic Symfony3 install so FOS User Bundle is out of the question at the moment.
Having dug around a bit...
There is a an arg at element index_0 on class Symfony\Component\DependencyInjection\ChildDefinition the object for the arg at element index_0 has an id of fos_user.user_provider.username_email.
The replace call then attempts to get the arguments of fos_user.user_provider.username_email, but there are none. Then the error occurs.
Any ideas?
TL;DR Move your service definitions below the autoloading definitions in services.yml and change the WsseFactory code to
$container
->setDefinition($providerId, new ChildDefinition(WsseProvider::class))
->setArgument('$userProvider', new Reference($userProvider))
;
Full explanation.
The first mistake in the supplied code is that the services definitions prepend the autoloading lines. The autoloading will just override the previous definitions and will cause the AuthenticationManagerInterface failure. Moving the definitions below will fix the issue. The other way to fix the issue is aliases as #yceruto and #gintko pointed.
But only that move will not make the code work despite on your answer. You probably didnt notice how changed something else to make it work.
The second issue, the failure of replaceArgument, is related to Symfony's order of the container compilation as was correctly supposed. The order is defined in the PassConfig class:
$this->optimizationPasses = array(array(
new ExtensionCompilerPass(),
new ResolveDefinitionTemplatesPass(),
...
$autowirePass = new AutowirePass(false),
...
));
I omitted the irrelevant passes. The security.authentication.provider.wsse.wsse_secured definition created by the WsseFactory is produced first. Then ResolveDefinitionTemplatesPass will take place, and will try to replace the arguments of the definition and raise the Cannot replace arguments exception you got:
foreach ($definition->getArguments() as $k => $v) {
if (is_numeric($k)) {
$def->addArgument($v);
} elseif (0 === strpos($k, 'index_')) {
$def->replaceArgument((int) substr($k, strlen('index_')), $v);
} else {
$def->setArgument($k, $v);
}
}
The issue will appear cause the pass will call Definition::replaceArgument for index_0. As the parent definition doesn't have an argument at position 0 neither in the former services.xml nor in the fixed one. AutowirePass wasn't executed yet, the autogenerated definitions has no arguments, the manual definition has the named $cachePool argument only.
So to fix the issue you could use rather:
->setArgument(0, new Reference($userProvider)); //proposed by #yceruto
->replaceArgument('$userProvider', new Reference($userProvider)); //proposed by #gintko
->setArgument('$userProvider', new Reference($userProvider)); // by me
All of them will entail the calls of Definition::addArgument or Definition::setArgument and will work out. There are only the little difference:
- setArgument(0, ... could not work for some other scenarios;
- I like ->setArgument('$userProvider' more than ->replaceArgument('$userProvider' due to semantics. Nothing to replace yet!
Hope the details why the issue appears now clear.
PS. There are also few other funny ways to overcome the issue.
Fix the config a bit:
AppBundle\Security\Authentication\Provider\WsseProvider:
arguments:
0: ''
$cachePool: '#cache.app'
public: false
Or set an alias of Symfony\Component\Security\Core\User\UserProviderInterface to let the autowire do the rest for you.
$container
->setDefinition($providerId, new ChildDefinition(WsseProvider::class))
// ->replaceArgument(0, new Reference($userProvider))
;
$container
->setAlias('Symfony\Component\Security\Core\User\UserProviderInterface',$userProvider)
;
This look like at this moment the provider definition doesn't have the autowired arguments ready, maybe related to the order in which the "CompilerPass" are processed, by now you can solve it with these little tweaks:
change this line in WsseFactory.php:
->replaceArgument(0, new Reference($userProvider))
by:
->setArgument(0, new Reference($userProvider))
and add this alias to services.yml to complete the autowired arguments of the new provider:
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface: '#security.authentication.manager'
EDIT: (deleted the previous contents due to the comment)
I see you don't use FOSUserBundle that makes things more comples. I did not yet create PR.
I think you are missing custom user entity. (see the link I have provided to create your own entity - my PR would be similar to these lines, if you are unable to create your own entity using my description and the link I'll provide PR, but that will take longer).
The steps you have to take:
Create your own User entity via src/AppBundle/Entity/User.php
Create DB table via php bin/console doctrine:schema:update --force
Configure Security to load your Entity - use AppBundle\Entity\User;
Then you have to create your own user. This can be tricky see password encoding for more.
(optional) Forbid interactive users If you want to login using
username OR email you can create Custom Query to Load the User
These steps should be enough for you to create your own user Entity and you don't have to use the FOSUserBundle.
EDIT:
Ok, so read some source code, and in ChildDefinition:100 you can see, that arguments are also indexed by argument's $name. So, it must be, that at compile time arguments of autowired services are passed with their named indexes, instead of numbered indexes.
WsseFactory.php
->replaceArgument(0, new Reference($userProvider));
so argument should be referenced by it's name:
->replaceArgument('$userProvider', new Reference($userProvider));
After this change, you will get new exception error, which says that it's not possible to autowire $authenticationManager in WsseListener service by it's interface. You can fix it simply by specifying alias for that interface in your services.yml:
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface: '#security.authentication.manager'
I guess this issue is related with new autowire feature, I will try to investigate it later why it is so.
For now, you can use old way to define services:
services.yml:
app.security.wsse_provider:
class: AppBundle\Security\Authentication\Provider\WsseProvider
arguments:
- ''
- '#cache.app'
public: false
app.security.wsse_listener:
class: AppBundle\Security\Firewall\WsseListener
arguments: ['#security.token_storage', '#security.authentication.manager']
public: false
in WsseFactory.php, following lines:
new ChildDefinition(WsseFactory::class);
new ChildDefinition(WsseListener::class);
translates into:
new ChildDefinition('app.security.wsse_provider');
new ChildDefinition('app.security.wsse_listener');
It seems the issue is because my config in services.yml came before the new auto wiring stuff.
So simply moving it below the auto wiring fixes it.
On editing a node with a user of a particular role i get the following error a Drupal site. With only user 1 it works. Can't figure why. I debug the entity file, but it seems that the id disappear after iteration.
EntityMetadataWrapperException : Invalid data value given. Be sure it matches the required data type and format. dans EntityDrupalWrapper->set() (ligne 737 dans /sites/all/modules/entity/includes/entity.wrapper.inc).
No coding yet has been done for this only using contrib modules.
Since you haven't done any coding, it's probably bug of some contrib module, Entity API or you're dealing with malformed entity.
Try applying this patch: Add field information to exception message on validation exception, this will allow you to see on which value it fails.
In case you're using some custom coding, then check Entity metadata wrappers page for some examples. The common mistake is to not use array() for set() when dealing with multi-valued field, or opposite - extra array for single-valued field.
If you can reproduce the problem, you can debug the issue either by step-by-step debugger and do the breakpoint on Exception line, or do it manually by temporary adding: var_dump(debug_backtrace()); exit; just before the exception happens, so PHP can dump the backtrace of your current code with all the argument passed, so you can track your malformed entity or identify the failing contrib module.
See also: How to set a value on a field collection using entity metadata wrapper at DA
I'm using .Net 4.5, running ASP.net Service configured with an Identity provider. I would like to alter the AudienceUri validation algorithm to allow wildcards or configure it to ignore subdomains.
In this page:
http://msdn.microsoft.com/en-us/library/system.identitymodel.selectors.samlsecuritytokenauthenticator.allowedaudienceuris(v=vs.100).Aspx
it says in the remarks:
Optionally, override the ValidateAudienceRestriction method to specify
the validation algorithm to use for the allowed URI.
I understand that I need to create a new class, inherit from SamlSecurityTokenAuthenticator and override the method.
But I'm not sure how do I apply this override, I believe that I should instantiate the new class and assign it to a static member of some class, such as FederationAuthentication, but I couldn't find the appropriate class.
Can anybody please clarify how to approach this?
There is a workaround for this.
If the RP validation of the received security token fails due to audience:
[System.InvalidOperationException]: {"ID1032: At least one 'audienceUri' must be specified in the SamlSecurityTokenRequirement when the AudienceUriMode is set to 'Always' or 'BearerKeyOnly'. Either add the valid URI values to the AudienceUris property of SamlSecurityTokenRequirement, or turn off checking by specifying an AudienceUriMode of 'Never' on the SamlSecurityTokenRequirement."}
Data: {System.Collections.ListDictionaryInternal}
HelpLink: null
HResult: -2146233079
InnerException: null
Message: "..."
StackTrace: ..
TargetSite: ..
}
This exception (and many others) can be handled in Application_Error, determine the exception using Server.GetLastError().
I noticed that:
FederatedAuthentication.WSFederationAuthenticationModule.SecurityTokenReceived
Fired right before this exception is thrown. This callback has SecurityTokenReceivedEventArgs, which has the SessionToken, where you can find all the audienceUri(s).
You can implement the logics you need there and add the relevant AudienceUri to:
FederatedAuthentication.ClaimsAuthorizationModule.FederationConfiguration.
IdentityConfiguration.AudienceRestriction.AllowedAudienceUris
As I said this is just a workaround, if you got can pour more information for my original question that will be great :)
Thanks.
Within Spring MVC, is there way to identify the 'level' or 'type' of error? Examples include information, warning, and error messages?
Btw, this is purely for formatting purposes. For example, error messages tend to be in red text while warning messages are simply black.
** Updating for clarity:
In our current system, we have multiple levels of messages: error, warning, info. Is there anything built into SpringMVC, that allows me to set an error message's level?
Also, it appears that we're using BindingResults in the Controllers (specifically BeanPropertyBindingResults), and triggering a Validator (our custom class that implements Spring's Validator interface).
During validation we end up writing something like:
ValidationUtils.rejectIfEmptyOrWhitespace(errors, modelVariable, "required", new Object[] {variableDisplayName});
Or
errors.rejectValue("field", "fieldName");
In the end, we ended up extending ObjectError and FieldError to include a new field, 'ErrorLevel'. When creating a new error -we use these new objects so that we can set the ErrorLevel. On the JSP, we look for the ErrorLevel to determine how to display the messages.
First of all, there's no such thing as 'level' or type of error in existing validation features.
But if you want to achieve level of error just like logging error levels, you can do so by setting the errors as list in request flashmap.
Eg.
RequestContextUtils.getOutputFlashMap(request).put("ERROR_MESSAGES", Arrays.asList("test error"));
RequestContextUtils.getOutputFlashMap(request).put("WARN_MESSAGES", Arrays.asList("test error"));