Symfony2 get to the access_control parameters located in the security.yml - php

I'm trying to get the access_control parameters which are located in my security.yml as an array in my custom service.
Just like with getting the role_hierarchy parameters I thought it would work with the following code:
$accessParameters = $this->container->getParameter('security.access_control');
Unfortunately this was not the case. Can someone tell how to get the parameters?

There's no way to get the access_control parameter from the container.
This is because this parameter is only used to create request matchers which will be registered as AccessMap later given in the AccessListener, and then are left over without registering it into the container.
You can try something hacky to get these matchers back by getting them like
$context = $this->get("security.firewall.map.context.main")->getContext();
$listener = $context[0][5];
// Do reflection on "map" private member
But this is kind of an ugly solution.
Another way I can see on how to get them is to parse again the security file
use Symfony\Component\Yaml\Yaml;
$file = sprintf("%s/config/security.yml", $this->container->getParameter('kernel.root_dir'));
$parsed = Yaml::parse(file_get_contents($file));
$access = $parsed['security']['access_control'];
If you want to register this configuration into a service, you can do something like
services.yml
services:
acme.config_provider:
class: Acme\FooBundle\ConfigProvider
arguments:
- "%kernel.root_dir%"
acme.my_service:
class: Acme\FooBundle\MyService
arguments:
- "#acme.config_provider"
Acme\FooBundle\ConfigProvider
use Symfony\Component\Yaml\Yaml;
class ConfigProvider
{
protected $rootDir;
public function __construct($rootDir)
{
$this->rootDir = $rootDir;
}
public function getConfiguration()
{
$file = sprintf(
"%s/config/security.yml",
$this->rootDir
);
$parsed = Yaml::parse(file_get_contents($file));
return $parsed['security']['access_control'];
}
}
Acme\FooBundle\MyService
class MyService
{
protected $provider;
public function __construct(ConfigProvider $provider)
{
$this->provider = $provider;
}
public function doAction()
{
$access = $this->provider->getConfiguration();
foreach ($access as $line) {
// ...
}
}
}

Necro, but still relevant. This is an improvement on Touki's answer above, where we don't reparse the access_control definitions, but rather use the already configured security token, firewall and access map to work out the answer.
.../services.yml
...
My\Application\AuthenticationBundle\Security\AccessControlHelper:
class: My\Application\AuthenticationBundle\Security\AccessControlHelper
arguments:
$securityContext: "#security.context"
$firewall: '#security.firewall.map'
$accessDecisionManager: '#security.access.decision_manager'
$accessMap: '#security.access_map'
...
src/My/Application/AuthenticationBundle/Security/AccessControlHelper.php
declare(strict_types=1);
namespace My\Application\AuthenticationBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\Security\Http\FirewallMapInterface;
class AccessControlHelper
{
/**
* #var SecurityContextInterface
*/
protected $securityContext;
/**
* #var FirewallMapInterface
*/
protected $firewallMap;
/**
* #var AccessDecisionManagerInterface
*/
protected $accessDecisionManager;
/**
* #var AccessMapInterface
*/
protected $accessMap;
public function __construct(
SecurityContextInterface $securityContext,
FirewallMapInterface $firewallMap,
AccessDecisionManagerInterface $accessDecisionManager,
AccessMapInterface $accessMap
)
{
$this->securityContext = $securityContext;
$this->firewallMap = $firewallMap;
$this->accessDecisionManager = $accessDecisionManager;
$this->accessMap = $accessMap;
}
public function isRequestAccessible(Request $request): bool
{
$token = $this->securityContext->getToken();
if (!$token || false == $token->isAuthenticated()) {
return false;
}
list($listeners) = $this->firewallMap->getListeners($request);
if ($listeners) {
foreach ($listeners as $listener) {
if ($listener instanceof AccessListener) {
/**
* Logic here is much inspired by the AccessListener->handle(...) method.
*/
list($attributes) = $this->accessMap->getPatterns($request);
if (null === $attributes) {
continue;
}
return boolval($this->accessDecisionManager->decide($token, $attributes, $request));
}
}
}
return true;
}
public function isUriAccessible(string $uri)
{
return $this->isRequestAccessible(Request::create($uri));
}
}
Sample usage:
use My\Application\AuthenticationBundle\Security\AccessControlHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
$container = ...; // #var ContainerInterface
$accessControlHelper = $container->get(AccessControlHelper::class);
$accessControlHelper->isRequestAccessible(new Request("/foo"));
$accessControlHelper->isUriAccessible("/foo");

Related

Update every doctrine query before it is sent to the database

We are running a huge platform that has a single database for multiple frontends. Now we are about to try to identify our slow queries and get an better idea of from what page our traffic comes from.
I had the idea to inject the page name as a comment in every sql query to be able to see it when looking at the database using SHOW FULL PROCESSLIST
At the end it should look like this: /*PAGE NAME*/ SHOW FULL PROCESSLIST
If I do this in sequel pro it seems that the comment gets listed then:
How can I update every doctrine query using a listener/subscriber to inject a custom comment?
Doctrine DBAL allows you to define your own Connection class.
doctrine:
dbal:
wrapper_class: App\DBAL\MyConnectionWrapper
You could implement a child class of Doctrine\DBAL\Connection and override executeQuery() according to your needs.
class MyConnectionWrapper extends Connection
{
public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
{
$sql = '/*PAGE NAME*/ '.$sql;
return parent::executeQuery($sql, $params, $types, $qcp);
}
}
Please check this Symfony documentation : Doctrine Event Listeners and Subscribers To understand the following code
Here is how i did it for each update, in order to update the update time:
<?php
namespace App\EventListener;
use App\Entity\AbstractEntity;
use App\Entity\User;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Exception;
use Stringable;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use function is_object;
class DatabaseActivitySubscriber implements EventSubscriber
{
/**
* #var TokenStorageInterface
*/
private TokenStorageInterface $tokenStorage;
/**
* #var null|User
*/
private ?User $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
$this->user = null;
}
/**
* #return array|string[]
*/
public function getSubscribedEvents()
{
return [
Events::prePersist,
Events::preUpdate,
];
}
/**
* Initiate the name of the user creating the object with "LastName FirstName (id)"
*
* #param LifecycleEventArgs $args
* #throws Exception
*/
public function prePersist(LifecycleEventArgs $args)
{
$object = $args->getObject();
if ($object instanceof AbstractEntity && $this->getUser() instanceof User) {
$object->setCreateUser($this->getUser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
$object->setCreateDate();
}
}
/**
* #return string|Stringable|UserInterface|null|User
*/
private function getUser()
{
if ($this->user instanceof User){
return $this->user;
}
$token = $this->tokenStorage->getToken();
if (null === $token) {
return null;
}
if (!is_object($user = $token->getUser())) {
return null;
}
$this->user = $user;
return $this->user;
}
/**
* #param LifecycleEventArgs $args
* #throws Exception
*/
public function preUpdate(LifecycleEventArgs $args)
{
$object = $args->getObject();
if ($object instanceof AbstractEntity && $this->getUser() instanceof User) {
$object->setUpdateUser($this->getuser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
$object->setUpdateDate();
}
}
}
And add in service.yaml:
App\EventListener\DatabaseActivitySubscriber:
tags:
- { name: 'doctrine.event_subscriber' }

How to use a DoctrineParamConverter from inside a Data Transformer Object class for a request?

I will start saying I am using Symfony 4.3.4 and Api Platform (called AP from now on). Having said that this how my custom controller (used for AP) looks like:
declare(strict_types=1);
namespace App\Controller\CaseWork\Pend;
use App\Request\PendCaseRequest;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
class PendCaseController
{
/**
* #Route("/myroute/{id}/pend", name="routeName")
* #ParamConverter("case", class="App\Entity\Cases")
*/
public function __invoke(PendCaseRequest $request, int $id)
{
// do something with the $request
}
}
As you may notice I also have a Request Data Transformer Object and here is a code snippet for it:
declare(strict_types=1);
namespace App\Request;
use App\Interfaces\RequestDTOInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints as Assert;
class PendCaseRequest implements RequestDTOInterface
{
/**
* #var int
*
* #Assert\NotBlank()
* #Assert\NotNull()
* #Assert\Type("integer")
*/
private $param;
public function __construct(Request $request)
{
$data = json_decode($request->getContent(), true);
$this->param = (int) $data['param'];
// ...
}
}
It's suppose (as per docs here) that when the request comes in and an id matching a App\Entity\Cases is found a new attribute named case should be append to my $request object but in my scenario is not happening and I am not sure why or what I am missing.
While debugging and setting a break point at this line $this->param = (int) $data['param']; in my DTO, if I print out $this->attributes I got the following output:
‌Symfony\Component\HttpFoundation\ParameterBag::__set_state(array(
'parameters' =>
array (
),
))
What I am missing here? What is wrong with my approach?
I have found a "solution" here. I end up using a Decorator as suggested by the answer on that post.
My main controller changed into this:
declare(strict_types=1);
namespace App\Controller\CaseWork\Pend;
use App\Request\PendCaseRequest;
use App\Entity\Cases;
class PendCaseController
{
public function __invoke(PendCaseRequest $request, Cases $case)
{
// do something with the $request
}
}
A decorator was created:
declare(strict_types=1);
namespace App\Decorator;
use App\Controller\CaseWork\Pend\PendCaseController;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Cases;
use App\Request\PendCaseRequest;
class PendCaseDecorator
{
/** #var PendCaseController */
protected $decoratedController;
/** #var EntityManagerInterface */
protected $entityManager;
public function __construct(PendCaseController $controller, EntityManagerInterface $entityManager)
{
$this->decoratedController = $controller;
$this->entityManager = $entityManager;
}
public function __invoke(PendCaseRequest $request, int $id)
{
$object = $this->entityManager->getRepository(Cases::class)->find($id);
if (!$object instanceof Cases) {
throw new NotFoundHttpException('Entity with '.$id.' not found');
}
return $this->decoratedController($request, $object);
}
}
And I had registered it at services.yml:
services:
App\Controller\CaseWork\Pend\PendCaseController: ~
App\Decorator\PendCaseDecorator:
decorates: App\Controller\CaseWork\Pend\PendCaseController
That way I keep using my DTO and pass back a Cases entity object.

Call to a member function on null, phone validation service

PHP with Symfony framework:
First of all before the context:
My input form is being built by form builder. Nothing is wrong there. So that is not the problem
I am making a sms validator system. I have a controller, and 2 services(validatorservice, smsapi(for api call)) Now my validatorservice looks like this:
class ValidatorService
{
public function validate($telefoonnummer)
{
$pregpatternNL = '(^\+[0-9]{2}|^\+[0-9]{2}\(0\)|^\(\+[0-9]{2}\)\(0\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\-\s]{10}$)';
if (!preg_match($pregpatternNL, $telefoonnummer)) {
return false;
} else {
return true;
}
}
}
Then my homecontroller:
use App\Service\ValidatorService;
class HomeController extends AbstractController
{
/** #var SmsApi */
private $smsApi;
/** #var validatorService */
private $validatorService;
public function __construct1(SmsApi $smsApi, Validatorservice
$validatorService)
{
$this->smsApi = $smsApi;
$this->validatorService = $validatorService;
}
/**
* #Route("/")
* #Template()
*
* #param Request $request
*
* #return array
*/
public function indexAction(Request $request)
{
$form = $this->createForm(
SmsLogFormType::class,
new SmsLog(),
[
'method' => 'post',
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** #var SmsLog $smslog */
$formData = $form->getData();
try {
$telefoonnummer = $formData->getTel();
$telefoonnummer = preg_replace('/[^0-9]/', '', $telefoonnummer);
$validatorservices = $this->validatorService-
>validate($telefoonnummer);
if ($validatorserviceres === false) {
$this->addFlash(
'notice',
'telefoonnummer onjuist formaat'
);
exit;
} else {
blablabla
}
Now whatever i try i get the error :
Call to a member function validate() on null
At first i thought maybe its something with the constructor names, but found online that that doesn't matter at all (also i didn't receive any code errors there)
Then i tried adding echo's to the if statement in my service. Maybe return true or false is seen as null but this doesn't work either.
I guess it's because of the number of arguments per constructor. If you define multiple constructors for a class, they should have different argument counts.
What you could do instead is to check whether or not the object you received is part of the wanted class/classes.
Or create static functions that instatiate the class with different object types.
EDIT
Use the default autowiring mechanisms:
private $smsApi;
private $validatorService;
public function __construct(SmsApi $smsApi, ValidatorService $validatorService)
{
$this->smsApi = $smsApi;
$this->validatorService = $validatorService;
}
It should work as intended if you change your Code to this :
/** #var SmsApi */
private $smsApi;
private $validatorService;
public function __construct(SmsApi $smsApi, ValidatorService $validatorService)
{
$this->validatorService = $validatorService;
$this->smsApi = $smsApi;
}
__construct1 and __construct2 are not native functions of php, so when the class is loaded, the constructors are not invoking and validatorService/smsApi are not being set (so they are null). The native function is called __construct.
/** #var SmsApi */
private $smsApi;
private $validatorService;
public function __construct(SmsApi $smsApi, ValidatorService $validatorService)
{
$this->smsApi = $smsApi;
$this->validatorService = $validatorService;
}
Or if doest not work, inject the services as arg in
public function indexAction(Request $request)
so...
public function indexAction(Request $request,SmsApi $smsApi, ValidatorService $validatorService)
and use $validatorService->validate();

Symfony: Access $_POST variable from service without going scope request

I need to access a $_POST variable from a service and I don't want to pass request as a paramter as I think this forces me to use scope: request in the service and I have bad memories from this scope from the past, as this forces a service instance per request, instead of one instance per application.
The thing is that $_POST works all right, but as symfony best practices recommends to avoid using php primitives I ask if there's a better way to do it (avoiding scope: request in service)
If you are using 2.4+ you can use the request_stack, see here.
You would use it like..
services.yml
your.service:
class: FQCN\To\Your\Service
arguments:
- #request_stack
FQCN\To\Your\Service
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class Service
{
/**
* #var RequestStack
*/
private $requestStack;
/**
* #var Request
*/
private $request;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function yourCall()
{
$param = $this->getRequest()->request->get('your-post-parameter');
//...
}
/**
* Get current request object
*
* #return Request
*/
private function getRequest()
{
if (null === $this->request) {
$this->request = $this->requestStack->getCurrentRequest();
}
return $this->request;
}
}
I don't see any problem with doing it like below which is what I did some time ago. You're not injecting whole Request.
CONTROLLER
public function searchByGetAction(Request $request)
{
//.......
$results = $this->productSearchService->findWithSimpleArray(
$request->query
);
//.......
}
public function searchByPostAction(Request $request)
{
//.......
$results = $this->productSearchService->findkWithNestedArray(
json_decode($request->getContent(), true)
);
//.......
}
SERVICE
public function findWithSimpleArray(ParameterBag $searchParameters)
{
$name = $searchParameters->get('name');
$surname = $searchParameters->get('surname');
//.......
}
public function findWithNestedArray($searchParameters = [])
{
$name = isset($searchParameters['name']) ? $searchParameters['name'] : null;
$surname = isset($searchParameters['surname']) ? $searchParameters['surname'] : null;
//.......
}

Add function that is called on each request in Symfony2

I need to add a function in Symfony2 that has to be called on each request. (language detection on requestion & session)
I thought to do this in the constructor of my Controller classes, but there the container is not known / created.
Have you suggestions for this?
You can define your Event Listener
Please, read documentation about event listeners creation.
Here is a listener that redirects to a page with the language set in the user configuration. Adapt it to your needs.
<?php
namespace MyVendor\Listener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Routing\RouterInterface;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\DiExtraBundle\Annotation\Observe;
/**
* #Service
*/
class LanguageListener
{
/**
* #var \Symfony\Component\Security\Core\SecurityContextInterface
*/
private $securityContext;
/**
* #var \Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* #InjectParams
*
* #param \Symfony\Component\Security\Core\SecurityContextInterface $securityContext
* #param \Symfony\Component\Routing\RouterInterface $router
*/
public function __construct(
SecurityContextInterface $securityContext,
RouterInterface $router
) {
$this->securityContext = $securityContext;
$this->router = $router;
}
/**
* #Observe("kernel.request")
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function forceLanguage(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$token = $this->securityContext->getToken();
if (!$token) {
return;
}
if (!$this->securityContext->isGranted('ROLE_USER')) {
return;
}
/** #var $request \Symfony\Component\HttpFoundation\Request */
$request = $event->getRequest();
$locale = $request->getLocale();
$route = $request->get('_route');
if ('_' === $route[0]) {
return;
}
/** #var $user \MyVendor\Model\User */
$user = $token->getUser();
if ($user->getConfig()->getLanguage() !== $locale) {
$parameters = array_merge($request->attributes->get('_route_params'), [
'_locale' => $user->getConfig()->getLanguage(),
]);
$path = $this->router->generate($route, $parameters);
$event->setResponse(new RedirectResponse($path));
}
}
}
I believe that it depends on what you are trying to do. For language detection most of the time symfony and its bundles handle virtually everything. That means that if you want to customize the routing you have to extend the routing component by using routing.loader tag..
However if you can use event listeners but I am not sure how many stuff you can change from there.
Either use events as suggested above or if you need something quick.
You can override setContainer method.
namespace My\Namespace;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyController extends Controller
{
private $foo;
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->foo = 'bar';
}
// your actions
}

Categories