security on two tables Symfony2 - php

I'm using Symfony 2.3 and I can't find a good solution to add a security on two tables.
I have an user and this user can get an application, he can access to his application with this path: /application/{id}
So I'd like to secure this page if the user is link to the application. I do something to check in my controller but this not very clean:
/**
*
* #param int $idApplication
* #return UserApplication
*/
public function testUserApplication($idApplication){
//get the application
$applicationRepository = $this->getDoctrine()->getRepository('PhoneApplicationBundle:Application');
$application = $applicationRepository->find($idApplication);
if($application==null){
return null;
}
$userApplicationRepository = $this->getDoctrine()->getRepository('PhoneApplicationBundle:UserApplication');
$userApplication = $userApplicationRepository->findOneBy(array(
'user' => $this->getUser(),
'application' => $application
));
return $userApplication;
}
I don't know if I can do this using the security.
I try an other solution creating a service which check this
class Test
{
/** #var \Doctrine\ORM\EntityManager */
private $doctrine;
/**
* Constructor
*
* #param Doctrine $doctrine
*/
public function __construct(Doctrine $doctrine)
{
$this->doctrine = $doctrine;
}
public function userApplication($idApplication){
//get the application
$applicationRepository = $this->doctrine->getRepository('PhoneApplicationBundle:Application');
$application = $applicationRepository->find($idApplication);
if($application==null){
return null;
}
$userApplicationRepository = $this->doctrine->getRepository('PhoneApplicationBundle:UserApplication');
$userApplication = $userApplicationRepository->findOneBy(array(
'user' => $this->getUser(),
'application' => $application
));
return $userApplication;
}
}
service.yml:
parameters:
phone_application.test_user_application.class: Phone\ApplicationBundle\Service\Test
services:
phone_application.test_user_application:
class: %phone_application.test_user_application.class%
arguments: [#doctrine]
But I don't realy understand how to use this in a controller
Thanks for help.

First
If you want to use a service in your controller, do
$serv = $this->get('nameoftheservice);
//then
$serv->yourFunctionOfTheService();
In your case :
$serv = $this->get('phone_application.test_user_application');
//then
$serv->userApplication($id);
Second
Another way to do it :
If the $user object is fully available in your controller just do :
//get the app
$application = $this->getDoctrine()->getRepository('PhoneApplicationBundle:Application')->find($idApplication);
//check if this user owns this app, considering you have sets the right doctrine annotation for relation in your entity file
if($user->getApplications()->contains($application)) {
//do your stuff
}
else
throw new \Exception('No right there ! ');

Related

Symfony : Mock LDAP Component in functional tests

I want to do functional tests on my Symfony (5.1) application, this application uses an Active Directory server as a "datas" database (creating , listing , updating datas). I'm using the Symfony ldap component. Code example below may contain typos.
Controller
class DatasController
{
/**
* #Route("/datas", name="datas")
* #IsGranted("ROLE_USER")
*
* #return Response
* #desc Displays LDAP datas
*/
public function datasList(DatasRepository $datasRepository)
{
$datas = $datasRepository->findAll();
return $this->render('datas/list.html.twig', [
'datas' => $datas,
]);
}
}
Repository
class DatasRepository
{
private Ldap $ldap;
private EntryManagerInterface $manager;
/**
* DatasRepository constructor.
* Service injected params
*/
public function __construct(Ldap $ldap, string $ldapAdminLogin, string $ldapAdminPwd)
{
$this->ldap = $ldap->bind($ldapAdminLogin, $ldapAdminPwd);
$this->manager = $ldap->getEntryManager();
}
public function create(Data $data): void
{
// ... some $data to Symfony\Component\Ldap\Entry $entry logic
$this->manager->add( $entry );
}
/**
* #return datas[]
*/
public function findAll()
{
$this->ldap->query('ou=test', '(&(objectclass=person))');
$entries = $query->execute()->toArray();
// ... some $entries to $datas logic
return $datas;
}
}
Test
class DatasControllerTest extends WebTestCase
{
public function testDatasList()
{
$client = static::createClient();
$client->request('GET', '/datas');
# Crash can't contact LDAP and thats logical
$this->assertResponseIsSuccessful();
}
}
So, how to do functional test on "GET /datas" ?
What part of the code should i mock to maximize test efficiency and coverage ?
Some additional information :
I can't have a dedicated LDAP server for tests (tests are run under
Docker via gitlab-ci)
I'm aware of the "don't mock what you don't
own".
I've read many posts/articles saying "you should mock the
LdapAdapter" but i have no idea on how to achieve this and haven't
found any example.
Any suggestion is welcome.
Thanks
Eric
About mockin external services: you can extend test service from the original one and make it methods behave how you want. Ex.:
class TestService extends \Symfony\OrAnyOtherExternalService
{
public function getConnection()
{
return new Connection([]);
}
}
then in your services_test.yaml change the class of this service to you tests service:
services:
Symfony\OrAnyOtherExternalService:
class: TestData\Services\TestService
this way in test environment application will use TestService instead of original

Laravel 7 set log path dynamically in Job class

Im building project on Laravel 7.3 with multiple Jobs that run at the same time.
I need to make each Job write logs to different daily rotated file. The name of the log file should be based on model, that Job is processing.
The issue is I cant find smart solution.
What I have tried:
1) creating multiple channels in config/logging.php.
That works as expected but at the moment there are about 50 different Jobs and amount keeps growing. Method is ugly and hardly maintained.
2) setting up Config(['logging.channels.CUSTOMCHANNEL.path' => storage_path('logs/platform/'.$this->platform->name.'.log')]);.
Messing with Config variable was bad idea because of many Jobs running one time. As a result messages from one job often were written in another Job log.
3) using Log::useDailyFiles()
Seems like this stops working since laravel 5.5 or 5.6. Just getting error Call to undefined method Monolog\Logger::useDailyFiles(). Any thoughts how to make with work in laravel 7?
4) using tap parameter for channel in config/logging.php.
Example in laravel docs
No ideas how to pass model name into CustomizeFormatter to setup file name.
Im almost sure there is smart solution and Im just missing something.
Any suggests? Thanks!
You could inherit the log manager to allow a dynamic configuration
<?php
namespace App\Log;
use Illuminate\Support\Str;
use Illuminate\Log\LogManager as BaseLogManager;
class LogManager extends BaseLogManager
{
/**
* Get the log connection configuration.
*
* #param string $name
* #return array
*/
protected function configurationFor($name)
{
if (!Str::contains($name, ':')) {
return parent::configurationFor($name);
}
[$baseName, $model] = explode(':', $name, 2);
$baseConfig = parent::configurationFor($baseName);
$baseConfig['path'] = ...; //your logic
return $baseConfig;
}
}
Likewise about Laravel's log service provider except this one can be totally replaced
<?php
namespace App\Log;
use Illuminate\Support\ServiceProvider;
class LogServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->singleton('log', function ($app) {
return new LogManager($app);
});
}
}
EDIT: I've just seen that Laravel's log service provider is missing from config/app.php, this is because it's "hard-loaded" by the application. You still can replace it by inheriting the application itself
<?php
namespace App\Foundation;
use App\Log\LogServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
use Illuminate\Foundation\Application as BaseApplication;
class Application extends BaseApplication
{
/**
* Register all of the base service providers.
*
* #return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
}
And finally in bootstrap/app.php, replace Illuminate\Foundation\Application with App\Foundation\Application
For example, if you try this
app('log')->channel('single:users')->debug('test');
Laravel will use the single channel's config and write to users.log if your resolution logic is
$baseConfig['path'] = $model + '.log';
I got a solution that I've been using since Laravel 4 that works, although it doesn't follow 'Laravel' way of doing things.
class UserTrackLogger
{
/**
* #var $full_path string
*/
protected $full_path;
/**
* #var $tenant string
*/
protected $tenant;
/**
* #var $user User
*/
protected $user;
/**
* #var $request Request
*/
protected $request;
public static function log(string $message, Request $request, User $user, array $data = []): void
{
/** #noinspection PhpVariableNamingConventionInspection */
$userTrack = new static($request, $user);
$userTrack->write($message, $data);
}
protected function __construct(Request $request, User $user)
{
$this->request = $request;
$this->user = $user;
$this->tenant = app()->make('tenant')->tenant__name;
$path = storage_path() . "/logs/{$this->tenant}/users";
$filename = $this->user->username_with_name;
$this->full_path = Formatter::formatPath("{$path}/{$filename}.log");
self::makeFolder($this->full_path);
}
protected function write(string $message, array $data = []): void
{
$formatter = $this->getFormat();
$record = [
'message' => $message,
'context' => $data,
'extra' => [],
'datetime' => date(Utility::DATETIME_FORMAT_DEFAULT),
'level_name' => 'TRACK',
'channel' => '',
];
file_put_contents($this->full_path, $formatter->format($record), FILE_APPEND);
}
protected function getFormat(): FormatterInterface
{
$ip = $this->request->getClientIp();
$method = strtoupper($this->request->method());
$format = "[%datetime%][{$this->tenant}][{$this->user->username}][{$this->user->name}]: $ip $method %message% %context%\n";
return new LineFormatter($format, null, true);
}
protected static function makeFolder(string $full_path): bool
{
$path = dirname($full_path);
if ( !is_dir($path) ) {
return mkdir($path, 0755, true);
}
return false;
}
}
And when I want to log something, I do UserTrackLogger::log($request->fullUrl(), $request, $user, $data);
What I would suggest is creating a logger similar to this but extends RotatingFileHandler.

ZF2 How to use global variables in the view

In ZF1 I used to declare variables in the application.ini
brandname = "Example"
weburl = "http://www.example.com/"
assetsurl = "http://assets.example.com/"
And in the Bootstrap I did this so i could access them in the view
define('BRANDNAME', $this->getApplication()->getOption("brandname"));
define('WEBURL', $this->getApplication()->getOption("weburl"));
define('ASSETSURL', $this->getApplication()->getOption("assetsurl"));
Whats the ZF2 way to do this, I know that i can create an array in the local.php config file like:
return array(
'example' => array(
'brandname' => 'Example',
'weburl' => 'http://www.example.com/',
'asseturl' => 'http://assets.example.com/',
),
);
When I want to access that variable in the controller I can do
$config = $this->getServiceLocator()->get('Config');
$config['example']['brandname']);
So far so good... but how do i access this variable in the view?
I don't want to create a view variable for it in every controller. And when i try the above in a view phtml file i get an error.
Zend\View\HelperPluginManager::get was unable to fetch or create an instance for getServiceLocator
Any ideas?
You could create a sinmple view helper to act as a proxy for your config, (totally un tested).
Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'configItem' => function ($helperPluginManager) {
$serviceLocator = $helperPluginManager->getServiceLocator();
$viewHelper = new View\Helper\ConfigItem();
$viewHelper->setServiceLocator($serviceLocator);
return $viewHelper;
}
),
);
}
ConfigItem.php
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceManager;
/**
* Returns total value (with tax)
*
*/
class ConfigItem extends AbstractHelper
{
/**
* Service Locator
* #var ServiceManager
*/
protected $serviceLocator;
/**
* __invoke
*
* #access public
* #param string
* #return String
*/
public function __invoke($value)
{
$config = $this->serviceLocator->get('config');
if(isset($config[$value])) {
return $config[$value];
}
return NULL;
// we could return a default value, or throw exception etc here
}
/**
* Setter for $serviceLocator
* #param ServiceManager $serviceLocator
*/
public function setServiceLocator(ServiceManager $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
}
You could then do something like this in your view, assuming you have them set in your config of course :)
echo $this->configItem('config_key');
echo $this->configItem('web_url');
I would personally tend to just pass the values through to the view every time though, keeping the view a dumb as possible.
I answered this before on a different post.
/* Inside your action controller method */
// Passing Var Data to Your Layout
$this->layout()->setVariable('stack', 'overflow');
// Passing Var Data to Your Template
$viewModel = new ViewModel(array( 'stack' => 'overflow' ));
/* In Either layout.phtml or {Your Template File}.phtml */
echo $this->stack; // Will print overview
That's it... No need to mess with view helpers, event manager, service manager, or anything else.
Enjoy!

Symfony2: Overriding createAction() in SonataAdmin

I've been googling as crazy the last days trying to figure out (with no success) how override a SonataAdmin action to capture the session username and save it in the foreign key field.
AttachmentAdminController class:
<?php
namespace Application\Sonata\UserBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
#use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\UserBundle\Entity\User;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Bridge\Monolog\Logger;
use Mercury\CargoRecognitionBundle\Entity\Attachment;
class AttachmentAdminController extends Controller
{
/**
* (non-PHPdoc)
* #see Sonata\AdminBundle\Controller.CRUDController::createAction()
*/
public function createAction()
{
$result = parent::createAction();
if ($this->get('request')->getMethod() == 'POST')
{
$flash = $this->get('session')->getFlash('sonata_flash_success');
if (!empty($flash) && $flash == 'flash_create_success')
{
#$userManager = $this->container->get('fos_user.user_manager');
#$user = $this->container->get('context.user');
#$userManager = $session->get('username');
$user = $this->container->get('security.context')->getToken()->getUser()->getUsername();
$attachment = new Attachment();
$attachment->setPath('/tmp/image.jpg');
$attachment->setNotes('nothing interesting to say');
$attachment->getSystemUser($user);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($product);
$em->flush();
}
}
return $result;
}
}
service attachment:
mercury.cargo_recognition.admin.attachment:
class: Mercury\CargoRecognitionBundle\Admin\AttachmentAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: General, label: Attachments }
arguments: [ null, Mercury\CargoRecognitionBundle\Entity\Attachment, "SonataAdminBundle:CRUD" ]
Seems to me as the actionController() is been ignored by SonataAdminBundle (and maybe the whole class file), because there's not error messages at all, but I don't know why. Actually, I'm not sure if I'm fetching the username from the session.
I really need a good tutorial about this, but seems like any information I get about this is obsolete in some aspect. By the way, I'm using Symfony 2.0.16
Finally I got to the solution. I'm sure there are some others (like using event listeners, for example, that seems to be simpler), but right now it's the best I could find (it works, and that's what matters).
I was trying to override the createAction() based on examples that I found in another forum thread, but I was getting two records in the table instead of one only. The most important thing was overriding the WHOLE action method and put the custom code in it.
Controller:
<?php
namespace Mercury\CargoRecognitionBundle\Controller;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Bridge\Monolog\Logger;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Application\Sonata\UserBundle\Entity\User;
use Mercury\CargoRecognitionBundle\Entity\Attachment;
use Mercury\CargoRecognitionBundle\Entity\SystemUser;
use Mercury\CargoRecognitionBundle\Repository\SystemUserRepository;
class AttachmentAdminController extends Controller
{
/**
* Set the system user ID
*/
private function updateFields($object)
{
$userName = $this->container->get('security.context')
->getToken()
->getUser()
->getUsername();
$user = $this->getDoctrine()
->getRepository('ApplicationSonataUserBundle:User')
->findOneByUsername($userName);
$object->setSystemUser($user);
return $object;
}
/**
* (non-PHPdoc)
* #see Sonata\AdminBundle\Controller.CRUDController::createAction()
*/
public function createAction()
{
// the key used to lookup the template
$templateKey = 'edit';
if (false === $this->admin->isGranted('CREATE')) {
throw new AccessDeniedException();
}
$object = $this->admin->getNewInstance();
$object = $this->updateFields($object);
// custom method
$this->admin->setSubject($object);
$form = $this->admin->getForm();
$form->setData($object);
if ($this->get('request')->getMethod() == 'POST') {
$form->bindRequest($this->get('request'));
$isFormValid = $form->isValid();
// persist if the form was valid and if in preview mode the preview was approved
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
$this->admin->create($object);
if ($this->isXmlHttpRequest()) {
return $this->renderJson(array(
'result' => 'ok',
'objectId' => $this->admin->getNormalizedIdentifier($object)
));
}
$this->get('session')->setFlash('sonata_flash_success','flash_create_success');
// redirect to edit mode
return $this->redirectTo($object);
}
// show an error message if the form failed validation
if (!$isFormValid) {
$this->get('session')->setFlash('sonata_flash_error', 'flash_create_error');
} elseif ($this->isPreviewRequested()) {
// pick the preview template if the form was valid and preview was requested
$templateKey = 'preview';
}
}
$view = $form->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->setTheme($view, $this->admin->getFormTheme());
return $this->render($this->admin->getTemplate($templateKey), array(
'action' => 'create',
'form' => $view,
'object' => $object,
));
}
}
Service for the controller:
mercury.cargo_recognition.admin.attachment:
class: Mercury\CargoRecognitionBundle\Admin\AttachmentAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: General, label: Attachments }
arguments: [ null, Mercury\CargoRecognitionBundle\Entity\Attachment, "MercuryCargoRecognitionBundle:AttachmentAdmin" ]
I took the solution from the following sites:
Sonata-Users,
Symfony framework forums,
(And the Sonata Project documentation)
It might be useful to override only the preCreate hook with your own logic:
/**
* This method can be overloaded in your custom CRUD controller.
* It's called from createAction.
*
* #param mixed $object
*
* #return Response|null
*/
protected function preCreate(Request $request, $object)
{
}

Zend Framework current route in bootstrap

Ladies and gentlemen,
I am working hard on a new webapplication which is based on the Zend Framework.
Almost the whole webapplication will be secured with a login(username and password).
My idea is to check if the visitor can be authenticated and if not check if the user is requesting a login route. If he is not trying to login he will be redirected to the login page.
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
/**
* Bootstrap::_initRouterRewrites()
*
* #return void
*/
protected function _initRouterRewrites()
{
$frontController = Zend_Controller_Front::getInstance();
$this->router = $frontController->getRouter();
$this->router->addRoute(
'default',
new Zend_Controller_Router_Route('/',
array('controller' => 'index',
'action' => 'index'))
)
->addRoute(
'login',
new Zend_Controller_Router_Route('/inloggen.html',
array('controller' => 'auth',
'action' => 'login'))
);
}
/**
* Bootstrap::_initZendSession()
*
* #return void
*/
protected function _initZendSession()
{
// Ensure that both the session and the db resources are bootstrapped
$this->bootstrap(array('db', 'session'));
// Actually start the session
Zend_Session::start();
}
/**
* Bootstrap::_initAuthentication()
*
* #return void
*/
protected function _initAuthentication()
{
// Instantiate auth object
$auth = Zend_Auth::getInstance();
// Check if visitor has an identity
if (!$auth->hasIdentity())
{
}
}
}
When I use the method $this->router->getCurrrentRoute() inthe _initAuthentication method I get the following error: "Current route is not defined".
What can I do to check if the current route is login?
Thanks in advance!
At the time of bootstrapping routing has run yet. What you need here is a FrontController Plugin that hooks into the request lifecycle at the appropriate place . In your case that is probably going to be during routeShutdown after the routing has determined where to dispatch the request.
From http://devzone.zend.com/1692/zend-framework-mvc-request-lifecycle/:
Use plugins like this in bootstrap.php:
protected function _initPlugins() {
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Plugin_Checkaccess());
}
and in plugins/Checkaccess.php
`
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$request = $this->getRequest();
$controllerName = strtolower($request->getControllerName());
$actionName = strtolower($request->getActionName());
if ('user' == $controllerName)
{
$session = new Zend_Session_Namespace('USER');
$notLogged = array('login','register');
if(!in_array($actionName, $notLogged) && !$session->user){
$request->setControllerName("index");
$request->setActionName("index");
$this->_response->setRedirect('/');
//$this->_response->setRedirect('/?redirectTo='.$this->_request->getRequestUri());
}elseif(in_array($actionName, $notLogged) && $session->user){
$request->setControllerName("user");
$request->setActionName("index");
$this->_response->setRedirect('/home');
}
return;
}
}
}`

Categories