Commit 85a4ebe7 authored by Christian BUFFIN's avatar Christian BUFFIN
Browse files

Corrections et améliorations du paramétrage des sous-traitants (plus travail...

Corrections et améliorations du paramétrage des sous-traitants (plus travail préparatoire), issue 361.
parent 44d110a5
......@@ -210,7 +210,7 @@ CREATE UNIQUE INDEX typages_organisations_typage_id_organisation_id_idx ON typag
INSERT INTO typages_organisations (typage_id, organisation_id)
SELECT typages.id, organisations.id
FROM typages
LEFT OUTER JOIN organisations ON (1 = 1);
INNER JOIN organisations ON (1 = 1);
ALTER TABLE fichiers ADD COLUMN typage_id INTEGER DEFAULT NULL REFERENCES typages(id) ON DELETE CASCADE ON UPDATE CASCADE;
......
......@@ -22,6 +22,8 @@
*/
App::uses('EtatFiche', 'Model');
App::uses('Inflector', 'Utility');
App::uses('LinkedOrganisationInterface', 'Model/Interface');
App::uses('ListeDroit', 'Model');
class DroitsComponent extends Component {
......@@ -91,6 +93,10 @@ class DroitsComponent extends Component {
return false;
}
public function isLogged() {
return empty($this->Session->read('Auth.User.id')) === false;
}
/**
* Vérification si l'user est DPO de son organisation
*
......@@ -303,7 +309,9 @@ class DroitsComponent extends Component {
*
* @param int $id
* @return boolean
*
*
* @deprecated
*
* @access public
* @created 29/04/2015
* @version V1.0.0
......@@ -323,6 +331,100 @@ class DroitsComponent extends Component {
return false;
}
public function assertNotSu()
{
if ($this->isSu() === true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
public function assertSu()
{
if ($this->isSu() !== true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
public function assertNotDpo()
{
if ($this->isDpo() === true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
public function assertDpo()
{
if ($this->isDpo() !== true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
public function assertLogged()
{
if ($this->isLogged() !== true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
public function assertAuthorized($level)
{
if ($this->authorized($level) !== true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
/**
* Vérification de l'existence de l'enregistrement.
*
* @param $modelClass
* @param $id
*/
public function assertRecordExists($modelClass, $id) {
$model = ClassRegistry::init($modelClass);
if ($id !== null) {
$query = [
'fields' => [$model->primaryKey],
'contain' => false,
'conditions' => [
"{$model->alias}.{$model->primaryKey}" => $id
]
];
$record = $model->find('first', $query);
if (empty($record) === true) {
throw new NotFoundException();
}
}
}
public function assertRecordAuthorized($modelClass, $id, array $params = []) {
$params += ['superadmin' => false,'dpo' => false];
$model = ClassRegistry::init($modelClass);
// Vérification de l'existence de l'enregistrement.
if ($id !== null) {
$this->assertRecordExists($modelClass, $id);
//@info: on ne bypass jamais pour le DPO ?
$bypass = (
($params['superadmin'] === true && $this->isSu() === true)
|| ($params['dpo'] === true && $this->isDpo() === true)
);
if ($bypass !== true) {
if ($modelClass === 'Organisation') {
$organisations = [$id];
} else {
if (is_subclass_of($model, 'LinkedOrganisationInterface') === false) {
$msgid = 'La classe de modèle %s doit implémenter l\'interface %s pour utiliser la méthode %s';
throw new RuntimeException(sprintf($msgid, $modelClass, 'LinkedOrganisationInterface', __METHOD__));
}
$organisations = $model->getLinkedOrganisationsIds($id);
}
if (in_array($this->Session->read('Organisation.id'), $organisations) !== true) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
}
}
/**
* Check si l'organisation en cours à un DPO déclaré
......@@ -356,4 +458,60 @@ class DroitsComponent extends Component {
return false;
}
/**
* Récupère et renvoie un enregistrement en fonction de l'utilisateur connecté (SU ou utilisateur appartenant à
* l'entité ayant créé l'enregistrement).
* L'enregistrement doit être lié à la classe Organisation par une relation hasAndBelongsToMany.
* Vérifie le cas échéant que celui-ci n'est associé à aucun entité.
*
* Utilisé pour vérification avant modification ou suppression d'un sous-traitant.
*
* @throws NotFoundException
* @throws ForbiddenException
*
* @todo: à utiliser pour d'autres contrôleurs
*
* @param string $modelClass Le nom de la classe de modèle de l'enregistrement
* @param int $id L'id de l'enregistrement
* @param bool $checkUnused Doit-on vérifier qu'aucune organisation n'est liée à l'enregistrement ?
* @return array
*/
public function getAndCheckLinkedOrganisationsRecord($modelClass, $id, $checkUnused)
{
$model = ClassRegistry::init($modelClass);
$query = [
'fields' => $model->fields(),
'contain' => [
'Organisation' => [
'fields' => [
'id'
]
]
],
'conditions' => [
"{$model->alias}.{$model->primaryKey}" => $id
]
];
$record = $model->find('first', $query);
if (empty($record) === true) {
throw new NotFoundException();
}
$record['Organisation'] = ['Organisation' => Hash::extract($record, 'Organisation.{n}.id')];
if ($checkUnused === true && count($record['Organisation']['Organisation']) > 0) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
// A-t-on les droits sur l'enregistrement ?
if ($this->isSu() === false) {
$creator = Hash::get($record, "{$model->alias}.createdbyorganisation");
if ($creator === null || $creator !== $this->Session->read('Organisation.id')) {
throw new ForbiddenException(__d('default', 'default.flasherrorPasDroitPage'));
}
}
return $record;
}
}
......@@ -49,7 +49,7 @@ class NotificationsComponent extends Component {
'vu' => false
)
));
if ($notification->save()) {
if ($notification->save(null, ['atomic' => true])) {
//$Email = new CakeEmail();
//$Email->config('email');
......
This diff is collapsed.
......@@ -13,7 +13,7 @@ msgstr ""
# ======================================================================================================================
####################### Controller/AdminsController.php #######################
####################### Controller/SoustraitantsController.php #######################
msgid "soustraitant.titreGestionSoustraitantApplication"
msgstr "Liste des sous-traitants présents dans toute l'application"
......@@ -22,7 +22,7 @@ msgid "soustraitant.titreGestionSoustraitantEntitee"
msgstr "Liste des sous-traitants présents dans l'entité"
msgid "soustraitant.titreAjouterSoustraitant"
msgstr "Ajouter un sous-traitant"
msgstr "Ajout d'un sous-traitant"
msgid "soustraitant.titreModificationSoustraitant"
msgstr "Modification d'un sous-traitant"
......@@ -36,11 +36,14 @@ msgstr "Visualisation d'un sous-traitant"
#### SUCCESS ####
msgid "soustraitant.flashsuccessSaveSoustraitant"
msgstr "Le sous-traitant a bien été enregistré"
msgid "soustraitant.flashsuccessDissocierSoustraitantEntite"
msgstr "La dissociation du sous-traitant de l'entité a été enregistrée"
msgid "soustraitant.flashsuccessSuppressionSoustraitantEntite"
msgstr "La suppression du sous-traitant pour l'entité a été enregistrée"
msgstr "La suppression du sous-traitant a été enregistrée"
msgid "soustraitant.flashsuccessSousTraitantAffecterEnregistrer"
msgstr "Le sous-traitant a été associé dans l'entité"
......@@ -49,6 +52,9 @@ msgstr "Le sous-traitant a été associé dans l'entité"
#### ERREUR ####
msgid "soustraitant.flasherrorSaveSoustraitant"
msgstr "Une erreur est survenue lors de l'enregistrement du sous-traitant"
msgid "soustraitant.flasherrorErreurDissocierSoustraitantEntite"
msgstr "Une erreur est survenue lors de la dissociation du sous-traitant de l'entité"
......@@ -73,6 +79,12 @@ msgstr " Filtrer les sous-traitants"
msgid "soustraitant.champFiltreEntite"
msgstr "Filtrer par entité"
msgid "soustraitant.champFiltreEntiteAssociee"
msgstr "Filtrer par entité associée"
msgid "soustraitant.champFiltreEntiteCreatrice"
msgstr "Filtrer par entité créatrice"
msgid "soustraitant.placeholderChoisirEntite"
msgstr "Choisir une entité"
......@@ -116,7 +128,7 @@ msgid "soustraitant.titreTableauRaisonSocialeSoustraitant"
msgstr "Raison sociale"
msgid "soustraitant.titreTableauEntiteSoustraitant"
msgstr "Entité"
msgstr "Entité(s) associée(s)"
msgid "soustraitant.titreTableauNumeroSiretSoustraitant"
msgstr "N° Siret"
......
<?php
interface LinkedOrganisationInterface
{
/**
* Retourne les id des entités liées à l'enregistrement.
*
* @param int $id
* @return array
*/
public function getLinkedOrganisationsIds($id);
/**
* Retourne une condition permettant de limiter aux enregistrements liés à une ou plusieurs entités.
*
* @param array|int $organisation_id
* @return string
*/
public function getConditionOrganisation($organisation_id);
}
......@@ -21,8 +21,9 @@
*/
App::uses('AppModel', 'Model');
App::uses('LinkedOrganisationInterface', 'Model/Interface');
class Soustraitant extends AppModel {
class Soustraitant extends AppModel implements LinkedOrganisationInterface {
public $name = 'Soustraitant';
......@@ -109,4 +110,71 @@ class Soustraitant extends AppModel {
'with' => 'SoustraitantOrganisation'
]
];
/**
* Retourne les id des entités liées au sous-traitant.
*
* @param int $id
* @return array
*/
public function getLinkedOrganisationsIds($id) {
$query = [
'fields' => ['organisation_id'],
'conditions' => ['soustraitant_id' => $id],
];
return Hash::extract(
$this->SoustraitantOrganisation->find('all', $query),
'{n}.SoustraitantOrganisation.organisation_id'
);
}
/**
* Retourne une condition permettant de limiter aux sous-traitants liés à une ou plusieurs entités.
*
* @param array|int $organisation_id
* @return string
*/
public function getConditionOrganisation($organisation_id) {
$query = [
'alias' => 'soustraitants_organisations',
'fields' => [
'soustraitants_organisations.soustraitant_id'
],
'conditions' => [
'soustraitants_organisations.organisation_id' => $organisation_id
]
];
$sql = $this->SoustraitantOrganisation->sql($query);
return "{$this->alias}.{$this->primaryKey} IN ( {$sql} )";
}
/**
* Retourne une liste (en clé et en valeur) uniques et triées pour un champ particulier.
*
* @todo: dans un behavior ?
*
* @param string $fieldName
* @param int $organisation_id
* @return array
*/
public function getStringOptionList($fieldName, $organisation_id) {
$query = [
'fields' => [
"{$this->alias}.{$fieldName}",
"{$this->alias}.{$fieldName}",
],
'conditions' => [],
'group' => [
"{$this->alias}.{$fieldName}",
],
'order' => ["{$this->alias}.{$fieldName} ASC"]
];
if ($organisation_id !== null) {
$query['conditions'][] = $this->getConditionOrganisation($organisation_id);
}
return $this->find('list', $query);
}
}
This diff is collapsed.
<?php
App::uses('AppController', 'Controller');
App::uses( 'ControllerTestCaseAccessTrait', 'Test/Trait/Controller' );
/**
* Tests d'intégration de la classe SoustraitantsController.
*
* ./cake_utils.sh tests app Controller/SoustraitantsController
*
* @package app.Test.Case.Controller
*/
class SoustraitantsControllerTest extends ControllerTestCase
{
use ControllerTestCaseAccessTrait;
public $fixtures = [
'app.Fiche',
'app.ListeDroit',
'app.Notification',
'app.Organisation',
'app.OrganisationUser',
'app.OrganisationUserRole',
'app.Role',
'app.RoleDroit',
'app.Soustraitant',
'app.SoustraitantOrganisation',
'app.User',
'app.Valeur',
];
public function setUp() {
parent::setUp();
$this->controller = $this->generate('Soustraitants');
}
public function dataAccessAdd() {
return [
// 1. Utilisateurs pouvant accéder à la fonctionnalité
[200, 'Superadministrateur.superadmin', '/soustraitants/add'],
[200, 'Administrateur.ibleu', '/soustraitants/add'],
[200, 'DPO.nroux', '/soustraitants/add'],
// 2. Utilisateurs ne pouvant pas accéder à la fonctionnalité
[403, 'Rédacteur.rjaune', '/soustraitants/add'],
[403, 'Valideur.cnoir', '/soustraitants/add'],
[403, 'Consultant.mrose', '/soustraitants/add'],
];
}
/**
* @dataProvider dataAccessAdd
*/
public function testAccessAdd($expectedStatus, $user, $url, $options = []) {
$this->assertActionAccess($expectedStatus, $user, $url, $options);
}
public function dataAccessDelete() {
return [
// 1. Utilisateurs pouvant accéder à la fonctionnalité
// 1.1. Enregistrement existant
// 1.1.1. Créé par le Superadmin
// 1.1.1.1. Non lié à une entité
[302, 'Superadministrateur.superadmin', '/soustraitants/delete/1'],
[403, 'Administrateur.ibleu', '/soustraitants/delete/1'],
[403, 'DPO.nroux', '/soustraitants/delete/1'],
// 1.1.1.2. Lié à une ou des entités
[403, 'Superadministrateur.superadmin', '/soustraitants/delete/2'],
[403, 'Administrateur.ibleu', '/soustraitants/delete/2'],
[403, 'DPO.nroux', '/soustraitants/delete/2'],
// 1.1.2. Créé par une entité
// 1.1.2.1. Non lié à une entité
[302, 'Superadministrateur.superadmin', '/soustraitants/delete/5'],
// 1.1.2.1.1. Pour l'entité créatrice
[302, 'Administrateur.ibleu', '/soustraitants/delete/5'],
[302, 'DPO.nroux', '/soustraitants/delete/5'],
// 1.1.2.1.2. Pour une autre entité que l'entité créatrice
[403, 'Administrateur.findigo', '/soustraitants/delete/5'],
[403, 'DPO.hvermeil', '/soustraitants/delete/5'],
// 1.1.2.2. Lié à une ou des entités
[403, 'Superadministrateur.superadmin', '/soustraitants/delete/6'],
[403, 'Administrateur.ibleu', '/soustraitants/delete/6'],
[403, 'DPO.nroux', '/soustraitants/delete/6'],
// 1.2. Enregistrement inexistant
[404, 'Superadministrateur.superadmin', '/soustraitants/delete/666'],
[404, 'Administrateur.ibleu', '/soustraitants/delete/666'],
[404, 'DPO.nroux', '/soustraitants/delete/666'],
// 2. Utilisateurs ne pouvant pas accéder à la fonctionnalité
// 2.1. Enregistrement existant
// 2.1.1. Créé par le Superadmin
// 2.1.1.1. Non lié à une entité
[403, 'Rédacteur.rjaune', '/soustraitants/delete/1'],
[403, 'Valideur.cnoir', '/soustraitants/delete/1'],
[403, 'Consultant.mrose', '/soustraitants/delete/1'],
// 2.1.1.2. Lié à une ou des entités
[403, 'Rédacteur.rjaune', '/soustraitants/delete/2'],
[403, 'Valideur.cnoir', '/soustraitants/delete/2'],
[403, 'Consultant.mrose', '/soustraitants/delete/2'],
// 2.1.2. Créé par une entité
// 2.1.2.1. Non lié à une entité
[403, 'Rédacteur.rjaune', '/soustraitants/delete/5'],
[403, 'Valideur.cnoir', '/soustraitants/delete/5'],
[403, 'Consultant.mrose', '/soustraitants/delete/5'],
// 2.1.2.2. Lié à une ou des entités
[403, 'Rédacteur.rjaune', '/soustraitants/delete/6'],
[403, 'Valideur.cnoir', '/soustraitants/delete/6'],
[403, 'Consultant.mrose', '/soustraitants/delete/6'],
// 2.2. Enregistrement inexistant
[403, 'Rédacteur.rjaune', '/soustraitants/delete/666'],
[403, 'Valideur.cnoir', '/soustraitants/delete/666'],
[403, 'Consultant.mrose', '/soustraitants/delete/666'],
];
}
/**
* @dataProvider dataAccessDelete
*/
public function testAccessDelete($expectedStatus, $user, $url, $options = []) {
$this->assertActionAccess($expectedStatus, $user, $url, $options);
}
public function dataAccessDissocierSoustraitant() {
return [
// 1. Utilisateurs pouvant accéder à la fonctionnalité
// 1.1. Enregistrement existant et associé
[302, 'Superadministrateur.superadmin', '/soustraitants/dissocierSoustraitant/2'],
[302, 'Administrateur.ibleu', '/soustraitants/dissocierSoustraitant/2'],
[302, 'DPO.nroux', '/soustraitants/dissocierSoustraitant/2'],
// Un utilisateur mono-collectivité ne peut pas accéder à l'enregistrement d'une autre collectivité
[403, 'Administrateur.findigo', '/soustraitants/dissocierSoustraitant/2'],
[403, 'DPO.hvermeil', '/soustraitants/dissocierSoustraitant/2'],
// 1.2. Enregistrement inexistant
[404, 'Superadministrateur.superadmin', '/soustraitants/dissocierSoustraitant/666'],
[404, 'Administrateur.ibleu', '/soustraitants/dissocierSoustraitant/666'],
[404, 'DPO.nroux', '/soustraitants/dissocierSoustraitant/666'],
// 2. Utilisateurs ne pouvant pas accéder à la fonctionnalité
// 1.1. Enregistrement existant
[403, 'Rédacteur.rjaune', '/soustraitants/dissocierSoustraitant/2'],
[403, 'Valideur.cnoir', '/soustraitants/dissocierSoustraitant/2'],
[403, 'Consultant.mrose', '/soustraitants/dissocierSoustraitant/2'],
// 2.2. Enregistrement inexistant
[403, 'Rédacteur.rjaune', '/soustraitants/dissocierSoustraitant/666'],
[403, 'Valideur.cnoir', '/soustraitants/dissocierSoustraitant/666'],
[403, 'Consultant.mrose', '/soustraitants/dissocierSoustraitant/666'],
];
}
/**
* @dataProvider dataAccessDissocierSoustraitant
*/
public function testAccessDissocierSoustraitant($expectedStatus, $user, $url, $options = []) {
$this->assertActionAccess($expectedStatus, $user, $url, $options);
}
public function dataAccessEdit() {
return [
// 1. Utilisateurs pouvant accéder à la fonctionnalité
// 1.1. Enregistrement existant
// 1.1.1. Créé par le Superadmin
// 1.1.1.1. Non lié à une entité
[200, 'Superadministrateur.superadmin', '/soustraitants/edit/1'],
[403, 'Administrateur.ibleu', '/soustraitants/edit/1'],
[403, 'DPO.nroux', '/soustraitants/edit/1'],
// 1.1.1.2. Lié à une ou des entités
[200, 'Superadministrateur.superadmin', '/soustraitants/edit/2'],
[403, 'Administrateur.ibleu', '/soustraitants/edit/2'],
[403, 'DPO.nroux', '/soustraitants/edit/2'],
// 1.1.2. Créé par une entité
// 1.1.2.1. Non lié à une entité
[200, 'Superadministrateur.superadmin', '/soustraitants/edit/5'],
// 1.1.2.1.1. Pour l'entité créatrice
[200, 'Administrateur.ibleu', '/soustraitants/edit/5'],
[200, 'DPO.nroux', '/soustraitants/edit/5'],
// 1.1.2.1.2. Pour une autre entité que l'entité créatrice
[403, 'Administrateur.findigo', '/soustraitants/edit/5'],
[403, 'DPO.hvermeil', '/soustraitants/edit/5'],
// 1.1.2.2. Lié à une ou des entités
[200, 'Superadministrateur.superadmin', '/soustraitants/edit/6'],
[200, 'Administrateur.ibleu', '/soustraitants/edit/6'],
[200, 'DPO.nroux', '/soustraitants/edit/6'],
// 1.2. Enregistrement inexistant
[404, 'Superadministrateur.superadmin', '/soustraitants/edit/666'],
[404, 'Administrateur.ibleu', '/soustraitants/edit/666'],
[404, 'DPO.nroux', '/soustraitants/edit/666'],
// 2. Utilisateurs ne pouvant pas accéder à la fonctionnalité
// 2.1. Enregistrement existant
// 2.1.1. Créé par le Superadmin
// 2.1.1.1. Non lié à une entité
[403, 'Rédacteur.rjaune', '/soustraitants/edit/1'],
[403, 'Valideur.cnoir', '/soustraitants/edit/1'],
[403, 'Consultant.mrose', '/soustraitants/edit/1'],
// 2.1.1.2. Lié à une ou des entités
[403, 'Rédacteur.rjaune', '/soustraitants/edit/2'],
[403, 'Valideur.cnoir', '/soustraitants/edit/2'],
[403, 'Consultant.mrose', '/soustraitants/edit/2'],
// 2.1.2. Créé par une entité
// 2.1.2.1. Non lié à une entité
[403, 'Rédacteur.rjaune', '/soustraitants/edit/5'],
[403, 'Valideur.cnoir', '/soustraitants/edit/5'],
[403, 'Consultant.mrose', '/soustraitants/edit/5'],
// 2.1.2.2. Lié à une ou des entités
[403, 'Rédacteur.rjaune', '/soustraitants/edit/6'],
[403, 'Valideur.cnoir', '/soustraitants/edit/6'],
[403, 'Consultant.mrose', '/soustraitants/edit/6'],
// 2.2. Enregistrement inexistant
[403, 'Rédacteur.rjaune', '/soustraitants/edit/666'],
[403, 'Valideur.cnoir', '/soustraitants/edit/666'],
[403, 'Consultant.mrose', '/soustraitants/edit/666'],
];
}
/**
* @dataProvider dataAccessEdit
*/
public function testAccessEdit($expectedStatus, $user, $url, $options = []) {