1. L'existant deux types d'utilisateurs du temps de DatamapperORM ================================================================= Nous avons tout d'abord un utilisateur "A&A" fourni dans le package d'authentification & autorisation CurrentUser: // application\third_party\currentuser\models\User.php class User extends DataMapper {} Ensuite nous nous donnions un utilisateur "applicatif" sans aucun lien avec notre package, créé comme modèle ordinaire (à l'héritage DatamapperORM près) sans pouvoir hériter de User ("l'objet de type Utilisateur casse le mapping réalisé par l'objet de type User avec ses classes collègues"). Cet utilisateur n'a donc aucune fonctionnalités de A&A et se consactre plutôt aux fonctionnalités que lui demannde le domaine, y compris se décliner le cas échéant en classes dérivées selon les besoins de l'application: // application\models\utilisateur.php & co. class Utilisateur extends DataMapper {} class UtilisateurX extends Utilisateur {} class UtilisateurY extends Utilisateur {} N'ayant pas su fusionner les deux pour des raisons tecniques, notre CI maison mettait à disposition les deux types simultanément en les rendant disponibles par défaut dans les contrôleurs: // application\core\MY_Controller.php class MY_Controller extends CI_Controller { public $CurrentUser; public $AppUser; ... $this->CurrentUser = \AuthenticationManager::GetCurrentUser(); // Une instance de User i.e. pour le A&A if ($this->CurrentUser !== false) { $this->AppUser = new Utilisateur($this->CurrentUser->id); // Une instance de Utilisateur i.e. applicatif } else { $this->AppUser = false; } } // application\third_party\currentuser\models\AuthenticationManager.php class AuthenticationManager { public static function GetCurrentUser() { $CIInstance = &get_instance(); $UserID = $CIInstance->session->userdata(CURRENTUSER_SESSION_ID); if ($UserID !== false && $UserID != 0) { return (new User($UserID)); } else { return false; } } } +++ IDEE: Si nous avons besoin d'un contrôle encore plus profond qui descend jusque dans le domain model même, nous pourrions similairement mettre à disposition de nos objets un objet CurrentUser via un MY_Model ! +++ 2. L'objectif monotype utilisateur aujourd'hui ============================================== Toujours et à la base un utilisateur "A&A" fourni par le package d'authentification & autorisation: // application\third_party\currentuser\models\User.php class User {} C'est cette classe qui sert de base à l'utilisateur "applicatif" maintenant qu'il n'y a plus de problème d'incompatibilité entre le comportement imposé par DatamapperORM à ses classes dérivées et le comportement désiré de nos classes utilisateurs: // application\models\utilisateur.php & co. class Utilisateur extends User {} class UtilisateurX extends Utilisateur {} class UtilisateurY extends Utilisateur {} N'ayant plus à choisir entre les deux, notre CI maison expose une et une seule instance utilisateur en le rendant disponibles par défaut dans les contrôleurs: // application\core\MY_Controller.php class MY_Controller extends CI_Controller { public $CurrentUser; $BaseUser = \AuthenticationManager::GetCurrentUser(); // Une instance de User i.e. pour le A&A if ($BasicUser !== false) { $this->CurrentUser = new Utilisateur($BasicUser->id); // Une instance de Utilisateur i.e. applicatif } else { $this->CurrentUser = false; } } 3. Les responsabilités existantes ================================= Pourquoi incohérence entre accepter un objet d'une part et accepter un identifiant d'autre part dans la même singature? 1. Un identifiant fait l'économie d'une instanciation. 2. Les méthodes sont symétriques et en fonction des instances à disposition on peut choisir la méthode qui permet d'utiliser soit l'instance soit l'identifiant. AuthenticationManager ::Login() ::Logout() ::GetCurrentUser() AuthorizationManager ::IsUserGrantedPrivilege(User $prmUser, $prmIdPrivilege) // DONE ::GetGrantedPrivileges(User $prmUser) ::IsUserAllowedOnFeature(User $prmUser, $prmIdFeature) // DONE ::GetAllowedFeatures(User $prmUser) ::IsPrivilegeGrantedToUser(Privilege $prmPrivilege, $prmIdUser) // DONE ::GetGrantedUsers(Privilege $prmPrivilege) // DONE ::IsPrivilegeAcceptedByFeature(Privilege $prmPrivilege, $prmIdFeature) // DONE ::GetAcceptingFeatures(Privilege $prmPrivilege) // DONE ::IsFeatureAcceptingPrivilege(Feature $prmFeature, $prmIdPrivilege) // DONE ::GetAcceptedPrivileges(Feature $prmFeature) // DONE ::IsFeatureAllowingUser(Feature $prmFeature, $prmIdUser) // DONE ::GetAllowedUsers(Feature $prmFeature) // DONE ::GetUsers[ID] // TODO ! ::GetPrivileges[ID] // TODO ! ::GetFeatures[ID] // TODO ! // A user is "granted" a privilege and is "allowed on" a feature User ->id ->Username ->Password ->IsDefined() // DONE ->IsUndefined() // DONE ->IsGrantedPrivilege($prmIdPrivilege) // DONE // Equivalent de IsUserGrantedPrivilege(User $prmUser, $prmIdPrivilege) ->GetGrantedPrivileges() // DONE ->IsAllowedOnFeature($prmIdFeature) // DONE // Equivalent de IsUserAllowedOnFeature(User $prmUser, $prmIdFeature) ->GetAllowedFeatures() // DONE // A privilege is "granted to" a user and "accepted by" a feature Privilege ->id ->Name ->IsGrantedToUser($prmIdUser) // DONE // Equivalent de IsPrivilegeGrantedToUser(Privilege $prmPrivilege, $prmIdUser) ->GetGrantedUsers() // DONE ->IsAcceptedByFeature($prmIdFeature) // DONE // Equivalent de IsPrivilegeAcceptedByFeature(Privilege $prmPrivilege, $prmIdFeature) ->GetAcceptingFeatures() // DONE // A feature "acccepts" a privilege and "allows" a user Feature ->id ->Name ->IsAcceptingPrivilege($prmIdPrivilege) // DONE // Equivalent de IsFeatureAcceptingPrivilege(Feature $prmFeature, $prmIdPrivilege) ->GetAcceptedPrivileges() // DONE ->IsAllowingUser($prmIdUser) // DONE // Equivalent de IsFeatureAllowingUser(Feature $prmFeature, $prmIdUser) ->GetAllowedUsers() // DONE 4. Les syntaxes existantes ou désirées pour le A&A ================================================== // Nous ne voulons plus d'une valeur false à la place d'un objet User lorsque personne n'est en session. // Nous voulons une homogénéité de type (User) au lieu de jongler entre deux types (User et boolean). OLD: if ($this->CurrentUser !== false) { ... } NEW: if (!$this->CurrentUser->IsDefined()) { ... } if (!$this->CurrentUser->IsUndefined()) { ... } // Rien n'empêche les instances de classe d'exposer les méthodes qui leur sont fournis par les services // i.e. en utilisant AuthorizationManager par dessous: ALT1: if (\AuthorizationManager::IsFeatureAllowed($this->CurrentUser, UI_MENU_VIREMENTSNOUVEAU)) { ... } ALT2: if ($SomeUser->IsFeatureAllowed(UI_MENU_VIREMENTSNOUVEAU)) { ... } ALT3: if ($SomeUser->IsAllowedOnFeature(UI_MENU_VIREMENTSNOUVEAU)) { ... } // Par extension, les classes User, Privilege et Feature devraient exposer l'ensemble des méthodes // d'AuthorizationManager en utilisant ce dernier par dessous. ... (voir ci-haut) 5. Meilleures pratiques pour des utilisateurs applicatifs différenciés ====================================================================== 1. Si on n'a aucun besoin sauf la sécurisation, sans rien d'autre que le login et le password, on peut se contenter d'utiliser directement la classe User de base et le cas échéant ses classes soeurs Feature, Privilege, ou même directement les services sous-jacents. Exemples: IsDefined(), IsUndefined(), IsPrivilegeGranted(), IsFeatureAllowed(), etc. 2. Dès qu'on a besoin d'informations et de comportements en plus pour l'utilisateur, à commencer par son *nom* pour les propriétés et un *CRUD* pour les comportemennts, on doit surclasser User. Exemples: ->Nom, ->Save() 3. Si l'application différencie différents types d'utilisateurs, on a le choix entre ne les différencier qu'à travers leurs privilèges (qui détermine leurs accès aux features), ou bien pousser la différenciation plus loin en crééant une classe pour chaque type. Ces classes peuvent théoriquement hériter directement de User, mais il est plus probable qu'elles partagent un socle commun comme décrit ci-dessus. Exemples: User <-- BaseUser <-- UserAgence, UserSolidis Idée: Penser aux abstract factories ? 6. Mettre à jour CurrentUser dans une application d'après le code de développement standalone de CurrentUser ============================================================================================================ Copier/mettre à jour vers l'application utilisatrice les codes suivants: IMPORTANT: Paramétrer vers un application/config/config_currentuser.php les éléments qui changent et charger automatiquement au travers de application/config/autoload.php::$autoload['config']. TODO: * application/config/config_currentuser.php: - Créer à partir de config_currentuser.php.default - Adapter les paramètres - Ajouter à SVN car ce sont des paramètres à l'application, pas pour le poste développeur. * application/config/autoload.php: - $autoload['config'] = array(..., "config_currentuser"); EXISTING: * application\controllers\authentication.php: - Attention à la différence de noms de colonnes de la table des utilisateurs dans login() une fois qu'ils sont paramétrés pour l'application. - Attention à préserver le code d'encryptage du mot de passe dans login() si l'application l'implémente: $MyUser->MotDePasse = Utilisateur::Encrypt($this->input->post("txtPassword")); - Attention à préserver le choix de la redirection après un logout si l'application a fait un choix différent de l'exemple fourni. * application\core\MY_Controller.php application\core\MY_Model.php application\core\MY_Output.php * application\third_party\currentuser - Attention à préserver noms de tables et colonnes dans constants.php une fois qu'ils sont paramétrés pour l'application. - TODO: C'est ce constants.php qui migre vers application/config/config_currentuser.php * Optionnel si on veut amener aussi les tests dans l'application cible: tests\fixtures\ibcu_users_fixt.yml tests\fixtures\ibcu_privileges_fixt.yml tests\fixtures\ibcu_features_fixt.yml tests\fixtures\ibcu_assusersprivileges_fixt.yml tests\fixtures\ibcu_assfeaturesprivileges_fixt.yml - ATTENTION à préserver les noms de colonnes dans les fixtures .yml une fois qu'ils sont paramétrés pour l'application. * Optionnel si on veut amener aussi les tests dans l'application cible: tests\models ==> tests\currentuser - NOTER bien qu'on envoie les tests du composant dans un répertoire dédié "currentuser" pour les distinguer plus facilement. - ATTENTION à préserver les noms de tables indiqués dans les chargements de fixture (var $tables = array(...)) une fois qu'il sont paramétrés pour l'application. - TODO: Lire les noms de tables de ficture dans application/config/config_currentuser.php aussi MISC: Syntaxes AppUser ====================== $this->AppUser->IsUtilisateurAgence() $this->AppUser->IsUtilisateurSiege() $this->AppUser->IsUtilisateurClient() (isset($this->AppUser) AND ($this->AppUser != null))