module access management

This commit is contained in:
Etienne Roudeix
2013-10-22 20:20:15 +02:00
parent 44a5909c81
commit dba4a129ff
9 changed files with 359 additions and 5 deletions

View File

@@ -28,7 +28,10 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Profile\ProfileEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Security\AccessManager;
use Thelia\Model\ModuleQuery;
use Thelia\Model\Profile as ProfileModel;
use Thelia\Model\ProfileModule;
use Thelia\Model\ProfileModuleQuery;
use Thelia\Model\ProfileQuery;
use Thelia\Model\ProfileResource;
use Thelia\Model\ProfileResourceQuery;
@@ -104,6 +107,30 @@ class Profile extends BaseAction implements EventSubscriberInterface
}
}
/**
* @param ProfileEvent $event
*/
public function updateModuleAccess(ProfileEvent $event)
{
if (null !== $profile = ProfileQuery::create()->findPk($event->getId())) {
ProfileModuleQuery::create()->filterByProfileId($event->getId())->delete();
foreach($event->getModuleAccess() as $moduleCode => $accesses) {
$manager = new AccessManager(0);
$manager->build($accesses);
$profileModule = new ProfileModule();
$profileModule->setProfileId($event->getId())
->setModule(ModuleQuery::create()->findOneByCode($moduleCode))
->setAccess( $manager->getAccessValue() );
$profileModule->save();
}
$event->setProfile($profile);
}
}
/**
* @param ProfileEvent $event
*/
@@ -129,6 +156,7 @@ class Profile extends BaseAction implements EventSubscriberInterface
TheliaEvents::PROFILE_UPDATE => array("update", 128),
TheliaEvents::PROFILE_DELETE => array("delete", 128),
TheliaEvents::PROFILE_RESOURCE_ACCESS_UPDATE => array("updateResourceAccess", 128),
TheliaEvents::PROFILE_MODULE_ACCESS_UPDATE => array("updateModuleAccess", 128),
);
}
}

View File

@@ -129,6 +129,7 @@
<form name="thelia.admin.profile.add" class="Thelia\Form\ProfileCreationForm"/>
<form name="thelia.admin.profile.modification" class="Thelia\Form\ProfileModificationForm"/>
<form name="thelia.admin.profile.resource-access.modification" class="Thelia\Form\ProfileUpdateResourceAccessForm"/>
<form name="thelia.admin.profile.module-access.modification" class="Thelia\Form\ProfileUpdateModuleAccessForm"/>
<form name="thelia.admin.template.creation" class="Thelia\Form\TemplateCreationForm"/>
<form name="thelia.admin.template.modification" class="Thelia\Form\TemplateModificationForm"/>

View File

@@ -781,6 +781,10 @@
<default key="_controller">Thelia\Controller\Admin\ProfileController::processUpdateResourceAccess</default>
</route>
<route id="admin.configuration.profiles.saveModuleAccess" path="/admin/configuration/profiles/saveModuleAccess">
<default key="_controller">Thelia\Controller\Admin\ProfileController::processUpdateModuleAccess</default>
</route>
<route id="admin.configuration.profiles.delete" path="/admin/configuration/profiles/delete">
<default key="_controller">Thelia\Controller\Admin\ProfileController::deleteAction</default>
</route>

View File

@@ -30,6 +30,7 @@ use Thelia\Core\Event\TheliaEvents;
use Thelia\Form\ProfileCreationForm;
use Thelia\Form\ProfileModificationForm;
use Thelia\Form\ProfileProfileListUpdateForm;
use Thelia\Form\ProfileUpdateModuleAccessForm;
use Thelia\Form\ProfileUpdateResourceAccessForm;
use Thelia\Model\ProfileQuery;
@@ -128,6 +129,16 @@ class ProfileController extends AbstractCrudController
return new ProfileUpdateResourceAccessForm($this->getRequest(), "form", $data);
}
protected function hydrateModuleUpdateForm($object)
{
$data = array(
'id' => $object->getId(),
);
// Setup the object form
return new ProfileUpdateModuleAccessForm($this->getRequest(), "form", $data);
}
protected function getObjectFromEvent($event)
{
return $event->hasProfile() ? $event->getProfile() : null;
@@ -246,9 +257,11 @@ class ProfileController extends AbstractCrudController
// Hydrate the form and pass it to the parser
$resourceAccessForm = $this->hydrateResourceUpdateForm($object);
$moduleAccessForm = $this->hydrateModuleUpdateForm($object);
// Pass it to the parser
$this->getParserContext()->addForm($resourceAccessForm);
$this->getParserContext()->addForm($moduleAccessForm);
}
return parent::updateAction();
@@ -264,6 +277,16 @@ class ProfileController extends AbstractCrudController
return $event;
}
protected function getUpdateModuleAccessEvent($formData)
{
$event = new ProfileEvent();
$event->setId($formData['id']);
$event->setModuleAccess($this->getModuleAccess($formData));
return $event;
}
protected function getResourceAccess($formData)
{
$requirements = array();
@@ -286,6 +309,28 @@ class ProfileController extends AbstractCrudController
return $requirements;
}
protected function getModuleAccess($formData)
{
$requirements = array();
foreach($formData as $data => $value) {
if(!strstr($data, ':')) {
continue;
}
$explosion = explode(':', $data);
$prefix = array_shift ( $explosion );
if($prefix != ProfileUpdateModuleAccessForm::MODULE_ACCESS_FIELD_PREFIX) {
continue;
}
$requirements[implode('.', $explosion)] = $value;
}
return $requirements;
}
public function processUpdateResourceAccess()
{
// Check current user authorization
@@ -334,4 +379,53 @@ class ProfileController extends AbstractCrudController
// At this point, the form has errors, and should be redisplayed.
return $this->renderEditionTemplate();
}
public function processUpdateModuleAccess()
{
// Check current user authorization
if (null !== $response = $this->checkAuth($this->resourceCode, AccessManager::UPDATE)) return $response;
$error_msg = false;
// Create the form from the request
$changeForm = new ProfileUpdateModuleAccessForm($this->getRequest());
try {
// Check the form against constraints violations
$form = $this->validateForm($changeForm, "POST");
// Get the form field values
$data = $form->getData();
$changeEvent = $this->getUpdateModuleAccessEvent($data);
$this->dispatch(TheliaEvents::PROFILE_MODULE_ACCESS_UPDATE, $changeEvent);
if (! $this->eventContainsObject($changeEvent))
throw new \LogicException(
$this->getTranslator()->trans("No %obj was updated.", array('%obj', $this->objectName)));
// Log object modification
if (null !== $changedObject = $this->getObjectFromEvent($changeEvent)) {
$this->adminLogAppend(sprintf("%s %s (ID %s) modified", ucfirst($this->objectName), $this->getObjectLabel($changedObject), $this->getObjectId($changedObject)));
}
if ($response == null) {
$this->redirectToEditionTemplate($this->getRequest(), isset($data['country_list'][0]) ? $data['country_list'][0] : null);
} else {
return $response;
}
} catch (FormValidationException $ex) {
// Form cannot be validated
$error_msg = $this->createStandardFormValidationErrorMessage($ex);
} catch (\Exception $ex) {
// Any other error
$error_msg = $ex->getMessage();
}
$this->setupFormErrorContext($this->getTranslator()->trans("%obj modification", array('%obj' => 'taxrule')), $error_msg, $changeForm, $ex);
// At this point, the form has errors, and should be redisplayed.
return $this->renderEditionTemplate();
}
}

View File

@@ -37,6 +37,7 @@ class ProfileEvent extends ActionEvent
protected $description = null;
protected $postscriptum = null;
protected $resourceAccess = null;
protected $moduleAccess = null;
public function __construct(Profile $profile = null)
{
@@ -139,4 +140,14 @@ class ProfileEvent extends ActionEvent
{
return $this->resourceAccess;
}
public function setModuleAccess($moduleAccess)
{
$this->moduleAccess = $moduleAccess;
}
public function getModuleAccess()
{
return $this->moduleAccess;
}
}

View File

@@ -553,6 +553,7 @@ final class TheliaEvents
const PROFILE_UPDATE = "action.updateProfile";
const PROFILE_DELETE = "action.deleteProfile";
const PROFILE_RESOURCE_ACCESS_UPDATE = "action.updateProfileResourceAccess";
const PROFILE_MODULE_ACCESS_UPDATE = "action.updateProfileModuleAccess";
// -- Tax Rules management ---------------------------------------------

View File

@@ -24,6 +24,7 @@
namespace Thelia\Core\Template\Loop;
use Propel\Runtime\ActiveQuery\Criteria;
use Thelia\Core\Security\AccessManager;
use Thelia\Core\Template\Element\BaseI18nLoop;
use Thelia\Core\Template\Element\LoopResult;
use Thelia\Core\Template\Element\LoopResultRow;
@@ -56,6 +57,13 @@ class Module extends BaseI18nLoop
{
return new ArgumentCollection(
Argument::createIntListTypeArgument('id'),
Argument::createIntTypeArgument('profile'),
new Argument(
'code',
new Type\TypeCollection(
new Type\AlphaNumStringListType()
)
),
new Argument(
'module_type',
new Type\TypeCollection(
@@ -89,6 +97,20 @@ class Module extends BaseI18nLoop
$search->filterById($id, Criteria::IN);
}
$profile = $this->getProfile();
if (null !== $profile) {
$search->leftJoinProfileModule('profile_module')
->addJoinCondition('profile_module', 'profile_module.PROFILE_ID=?', $profile, null, \PDO::PARAM_INT)
->withColumn('profile_module.access', 'access');
}
$code = $this->getCode();
if(null !== $code) {
$search->filterByCode($code, Criteria::IN);
}
$moduleType = $this->getModule_type();
if (null !== $moduleType) {
@@ -129,6 +151,16 @@ class Module extends BaseI18nLoop
->set("CLASS", $module->getFullNamespace())
->set("POSITION", $module->getPosition());
if (null !== $profile) {
$accessValue = $module->getVirtualColumn('access');
$manager = new AccessManager($accessValue);
$loopResultRow->set("VIEWABLE", $manager->can(AccessManager::VIEW)? 1 : 0)
->set("CREATABLE", $manager->can(AccessManager::CREATE) ? 1 : 0)
->set("UPDATABLE", $manager->can(AccessManager::UPDATE)? 1 : 0)
->set("DELETABLE", $manager->can(AccessManager::DELETE)? 1 : 0);
}
$loopResult->addRow($loopResultRow);
}

View File

@@ -0,0 +1,99 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
namespace Thelia\Form;
use Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\ExecutionContextInterface;
use Thelia\Core\Security\AccessManager;
use Thelia\Core\Translation\Translator;
use Thelia\Model\ProfileQuery;
use Thelia\Model\ModuleQuery;
/**
* Class ProfileUpdateModuleAccessForm
* @package Thelia\Form
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*/
class ProfileUpdateModuleAccessForm extends BaseForm
{
const MODULE_ACCESS_FIELD_PREFIX = "module";
protected function buildForm($change_mode = false)
{
$this->formBuilder
->add("id", "hidden", array(
"required" => true,
"constraints" => array(
new Constraints\NotBlank(),
new Constraints\Callback(
array(
"methods" => array(
array($this, "verifyProfileId"),
),
)
),
)
))
;
foreach(ModuleQuery::create()->find() as $module) {
$this->formBuilder->add(
self::MODULE_ACCESS_FIELD_PREFIX . ':' . str_replace(".", ":", $module->getCode()),
"choice",
array(
"choices" => array(
AccessManager::VIEW => AccessManager::VIEW,
AccessManager::CREATE => AccessManager::CREATE,
AccessManager::UPDATE => AccessManager::UPDATE,
AccessManager::DELETE => AccessManager::DELETE,
),
"attr" => array(
"tag" => "modules",
"module_code" => $module->getCode(),
"module_title" => $module->getTitle(),
),
"multiple" => true,
"constraints" => array(
)
)
);
}
}
public function getName()
{
return "thelia_profile_module_access_modification";
}
public function verifyProfileId($value, ExecutionContextInterface $context)
{
$profile = ProfileQuery::create()
->findPk($value);
if (null === $profile) {
$context->addViolation("Profile ID not found");
}
}
}

View File

@@ -32,7 +32,8 @@
<ul class="nav nav-tabs clearfix">
<li {if $oder_tab == 'data'}class="active"{/if}><a href="#data" data-tab-name="cart" data-toggle="tab"><span class="glyphicon glyphicon-shopping-cart"></span> {intl l="Description"}</a></li>
<li {if $oder_tab == 'permissions'}class="active"{/if}><a href="#permissions" data-tab-name="bill" data-toggle="tab"><span class="glyphicon glyphicon-list-alt"></span> {intl l="Permissions"}</a></li>
<li {if $oder_tab == 'resources'}class="active"{/if}><a href="#resources" data-tab-name="bill" data-toggle="tab"><span class="glyphicon glyphicon-list-alt"></span> {intl l="Resource access rights"}</a></li>
<li {if $oder_tab == 'modules'}class="active"{/if}><a href="#modules" data-tab-name="bill" data-toggle="tab"><span class="glyphicon glyphicon-list-alt"></span> {intl l="Module access rights"}</a></li>
</ul>
<div class="tab-content">
@@ -116,11 +117,11 @@
</div>
<div class="tab-pane fade {if $oder_tab == 'permissions'}active in{/if}" id="permissions">
<div class="tab-pane fade {if $oder_tab == 'resources'}active in{/if}" id="resources">
{form name="thelia.admin.profile.resource-access.modification"}
<form method="POST" action="{url path="/admin/configuration/profiles/saveResourceAccess?tab=permissions"}" {form_enctype form=$form} >
<form method="POST" action="{url path="/admin/configuration/profiles/saveResourceAccess?tab=resources"}" {form_enctype form=$form} >
{form_hidden_fields form=$form}
@@ -131,7 +132,8 @@
<table class="table table-striped table-condensed table-left-aligned">
<caption>
{intl l="Manage permissions"}
{intl l="Manage resource rights"}
<button type="submit" class="btn btn-default btn-primary pull-right"><span class="glyphicon glyphicon-check"></span> {intl l="Save"}</button>
</caption>
<thead>
<tr>
@@ -184,7 +186,89 @@
</tbody>
<tfoot>
<tr>
<td colspan="3">
<td colspan="7">
<button type="submit" class="btn btn-default btn-primary pull-right"><span class="glyphicon glyphicon-check"></span> {intl l="Save"}</button>
</td>
</tr>
</tfoot>
</table>
</form>
{/form}
</div>
<div class="tab-pane fade {if $oder_tab == 'modules'}active in{/if}" id="modules">
{form name="thelia.admin.profile.module-access.modification"}
<form method="POST" action="{url path="/admin/configuration/profiles/saveModuleAccess?tab=modules"}" {form_enctype form=$form} >
{form_hidden_fields form=$form}
{* Be sure to get the product ID, even if the form could not be validated *}
<input type="hidden" name="profile_id" value="{$ID}" />
{if $form_error}<div class="alert alert-danger">{$form_error_message}</div>{/if}
<table class="table table-striped table-condensed table-left-aligned">
<caption>
{intl l="Manage module rights"}
<button type="submit" class="btn btn-default btn-primary pull-right"><span class="glyphicon glyphicon-check"></span> {intl l="Save"}</button>
</caption>
<thead>
<tr>
<th rowspan="2">{intl l="Module"}</th>
<th rowspan="2">{intl l="Title"}</th>
<th colspan="4" class="text-center">{intl l="Rights"}</th>
</tr>
<tr>
<th>{intl l="View"}</th>
<th>{intl l="Create"}</th>
<th>{intl l="Update"}</th>
<th>{intl l="Delete"}</th>
</tr>
</thead>
<tbody>
{form_tagged_fields form=$form tag='modules'}
{loop type="module" name="module-list" code=$attr_list.module_code profile=$ID backend_context="1"}
<tr>
<td>{$CODE}</td>
<td>{$TITLE}</td>
<td>
<div class="make-switch switch-mini" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input name="{$name}" value="VIEW" type="checkbox" {if $VIEWABLE == 1}checked="checked"{/if}>
</div>
</td>
<td>
<div class="make-switch switch-mini" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input name="{$name}" value="CREATE" type="checkbox" {if $CREATABLE == 1}checked="checked"{/if}>
</div>
</td>
<td>
<div class="make-switch switch-mini" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input name="{$name}" value="UPDATE" type="checkbox" {if $UPDATABLE == 1}checked="checked"{/if}>
</div>
</td>
<td>
<div class="make-switch switch-mini" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input name="{$name}" value="DELETE" type="checkbox" {if $DELETABLE == 1}checked="checked"{/if}>
</div>
</td>
</tr>
{/loop}
{/form_tagged_fields}
</tbody>
<tfoot>
<tr>
<td colspan="7">
<button type="submit" class="btn btn-default btn-primary pull-right"><span class="glyphicon glyphicon-check"></span> {intl l="Save"}</button>
</td>
</tr>