Index: main/plugin-core/i18n/messages_en.xml
===================================================================
--- main/plugin-core/i18n/messages_en.xml	(revision 33858)
+++ main/plugin-core/i18n/messages_en.xml	(working copy)
@@ -66,6 +66,10 @@
          + -->
     <message key="PLUGINS_CORE_RIGHTS_USERS_LABEL">Users management</message>
     <message key="PLUGINS_CORE_RIGHTS_USERS_DESCRIPTION">Users management</message>
+    
+    <message key="PLUGINS_CORE_RIGHTS_USERS_IMPERSONATE_LABEL">Impersonate</message>
+    <message key="PLUGINS_CORE_RIGHTS_USERS_IMPERSONATE_DESCRIPTION">Connect with the login of the selected user.</message>
+
     <!-- Ribbon -->
     <message key="RIBBON_TABS_TAB_RIGHTS_HANDLE_LABEL">Users, groups and rights</message>
     <message key="RIBBON_TABS_TAB_USERS_GROUPS_GROUP_USERS_LABEL">CMS users</message>
@@ -92,6 +96,7 @@
     <message key="PLUGINS_CORE_USERS_DELETE_CONFIRM">Are you sure you want to delete these(s) user(s)?</message>
     <message key="PLUGINS_CORE_USERS_DELETE_ERROR">An error occurred: unable to delete user(s)</message>
     <message key="PLUGINS_CORE_USERS_INFOS_ERROR">An error occurred while retrieving user's information</message>
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_ERROR">An error occurred: unable to connect with the login of the selected user.</message>
     <!-- Helper -->
     <message key="PLUGINS_CORE_USERS_DIALOG_ADD_TITLE">New user ...</message>
     <message key="PLUGINS_CORE_USERS_DIALOG_EDIT_TITLE">Edit ...</message>
@@ -125,6 +130,14 @@
     <message key="PLUGINS_CORE_UITOOL_USER_RIGHTS_RIGHTS_BY_CONTEXT_HINT_2">&lt;/b&gt;.&lt;br/&gt;Each right is assigned on one or more contexts.</message>
     <message key="PLUGINS_CORE_UITOOL_USER_RIGHTS_ERROR">An error occurred : the user rights can not be displayed</message>
     
+    <!-- +
+         | IMPERSONATE USER
+         + -->
+    <message key="PLUGINS_CORE_UITOOL_USER_IMPERSONATE_LABEL">Impersonate</message>
+    <message key="PLUGINS_CORE_UITOOL_USER_IMPERSONATE_DESCRIPTION">Connect to the CMS with the login of the selected user.</message> 
+    <message key="PLUGINS_CORE_UITOOL_USER_IMPERSONATE_MULTI_FORBIDDEN">Several users are currently selected.&lt;br/&gt;Select a single user in order to be able to connect with his login</message>
+    
+    
 	<!-- USER JDBC -->
 	<message key="PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_LABEL">Login ID</message>
 	<message key="PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_DESCRIPTION">Unique login ID for user&lt;br/&gt;3-64 characters (letters, digits or '_')</message>
@@ -223,8 +236,14 @@
     <message key="PLUGINS_CORE_PROFILES_SWITCH_TO_EDITION_DESCRIPTION">Switches to editing mode to edit rights (addition or deletion) of the selected profile</message>
     <message key="PLUGINS_CORE_PROFILES_SWITCH_TO_VIEW">Overview</message>
     <message key="PLUGINS_CORE_PROFILES_SWITCH_TO_VIEW_DESCRIPTION">Switches to view mode of the profile's rights</message>
-    <message key="PLUGINS_CORE_PROFILES_HANDLE_SAVE_MODIFICATIONS">Save</message>
-    <message key="PLUGINS_CORE_PROFILES_HANDLE_SAVE_MODIFICATIONS_HINT">Save changes on profile</message>
+    <message key="PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS">Save</message>
+    <message key="PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS_DESCRIPTION">Save changes on profile</message>
+    <message key="PLUGINS_CORE_PROFILES_DONT_SAVE_MODIFICATIONS">Discard changes</message>
+    <message key="PLUGINS_CORE_PROFILES_DONT_SAVE_MODIFICATIONS_DESCRIPTION">Discard the current changes of the profile.</message>
+    
+    <message key="PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_TITLE">Save</message>
+    <message key="PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_DESCRIPTION">The attributed rights have been modified. Do you wish to save those changes ?</message>
+    
     <message key="PLUGINS_CORE_PROFILES_SELECT_ALL">Select all</message>
     <message key="PLUGINS_CORE_PROFILES_SELECT_ALL_DESCRIPTION">Select all rights</message>
     <message key="PLUGINS_CORE_PROFILES_UNSELECT_ALL">Unselect all</message>
@@ -293,6 +312,18 @@
 	<message key="PLUGINS_CORE_RIGHTS_CONTEXTS_APPLICATION_HELP_AFFECT">In order to assign profiles, a user/group can be selected from the list.</message>
 	<message key="PLUGINS_CORE_RIGHTS_CONTEXTS_APPLICATION_HELP_PROFIL">Select profiles that you want to assign to users/groups.&lt;br/&gt;Click on the link 'Confirm changes' to save the assignments.</message>
 
+    <!-- SUPER USER -->
+    <message key="PLUGINS_CORE_SUPERUSER_CATEGORY">Super user</message>
+    <message key="PLUGINS_CORE_RIGHTS_SUPERUSER_LABEL">Assign a super user</message>
+    <message key="PLUGINS_CORE_RIGHTS_SUPERUSER_DESCRIPTION">Right to assign a super user</message>
+
+    <message key="PLUGINS_CORE_SUPERUSER_LABEL">Assign super user</message>
+    <message key="PLUGINS_CORE_SUPERUSER_DESCRIPTION">Set the administrator of the site.</message>
+    <message key="PLUGINS_CORE_SUPERUSER_AFFECT_ERROR">An error occurred: the allocation has failed.</message>
+    <message key="PLUGINS_CORE_SUPERUSER_AFFECT_TITLE">Assign super user</message>
+    <message key="PLUGINS_CORE_SUPERUSER_AFFECT_SUCCESS">The allocation has been completed.</message>
+
+
 	<!-- RegExp -->
 	<message key="PLUGINS_CORE_REGEXP_INVALID_MAIL">The address is not a valid email address</message>
 	<message key="PLUGINS_CORE_REGEXP_INVALID_URL">The url is invalid. It has to start with either http:// or https://</message>
Index: main/plugin-core/i18n/messages_fr.xml
===================================================================
--- main/plugin-core/i18n/messages_fr.xml	(revision 33858)
+++ main/plugin-core/i18n/messages_fr.xml	(working copy)
@@ -59,12 +59,16 @@
 
     <!-- USER AND RIGHTS -->
     <message key="PLUGINS_CORE_USERANDRIGHTS_CATEGORY">Utilisateurs et droits</message>
-
+    
 	<!-- +
          | USERS 
          + -->
     <message key="PLUGINS_CORE_RIGHTS_USERS_LABEL">Gestion des utilisateurs</message>
     <message key="PLUGINS_CORE_RIGHTS_USERS_DESCRIPTION">Gestion des utilisateurs</message>
+    
+    <message key="PLUGINS_CORE_RIGHTS_USERS_IMPERSONATE_LABEL">Prendre le contrôle</message>
+    <message key="PLUGINS_CORE_RIGHTS_USERS_IMPERSONATE_DESCRIPTION">Se connecter avec les identifiants de l'utilisateur choisi.</message>
+    
     <!-- Ribbon -->
     <message key="RIBBON_TABS_TAB_RIGHTS_HANDLE_LABEL">Utilisateurs, groupes et droits</message>
     <message key="RIBBON_TABS_TAB_USERS_GROUPS_GROUP_USERS_LABEL">Utilisateurs du CMS</message>
@@ -91,6 +95,7 @@
     <message key="PLUGINS_CORE_USERS_DELETE_CONFIRM">Etes-vous sûr de vouloir supprimer ce ou ces utilisateur(s) ?</message>
     <message key="PLUGINS_CORE_USERS_DELETE_ERROR">Une erreur est survenue sur le serveur. Impossible de supprimer le ou les utilisateur(s).</message>
     <message key="PLUGINS_CORE_USERS_INFOS_ERROR">Une erreur est survenue: impossible de récupérer les informations sur cet utilisateur</message>
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_ERROR">Une erreur est survenue: impossible de se connecter avec les identifiants de l'utilisateur sélectionné.</message>
     <!-- Helper -->
     <message key="PLUGINS_CORE_USERS_DIALOG_ADD_TITLE">Nouvel utilisateur ...</message>
     <message key="PLUGINS_CORE_USERS_DIALOG_EDIT_TITLE">Modifier ...</message>
@@ -123,6 +128,16 @@
     <message key="PLUGINS_CORE_UITOOL_USER_RIGHTS_RIGHTS_BY_CONTEXT_HINT_2">&lt;/b&gt;.&lt;br/&gt;Chaque droit peut-être affecté sur un ou plusieurs contextes.</message>
     <message key="PLUGINS_CORE_UITOOL_USER_RIGHTS_ERROR">Une erreur est survenue : elle a empêchée l'affichage des droits de l'utilisateur</message>
     
+    <!-- +
+         | IMPERSONATE USER
+         + -->
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_LABEL">Prendre le contrôle</message>
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_DESCRIPTION">Se connecter au CMS avec les identifiants de l'utilisateur sélectionné.</message> 
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_MULTI_FORBIDDEN">Plusieurs utilisateurs sont actuellement sélectionnés.&lt;br/&gt;Sélectionnez un seul utilisateur afin de pouvoir se connecter avec ses identifiants.</message>
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_SUCCESS_TITLE">Prise de contrôle réussie</message>
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_SUCCESS">Vous êtes maintenant connecté avec les identifiants suivants : </message>
+    <message key="PLUGINS_CORE_USERS_IMPERSONATE_SUCCESS_2">Vous allez être redirigé vers l'application.</message>
+    
     <!-- USER JDBC -->
     <message key="PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_LABEL">Identifiant</message>
     <message key="PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_DESCRIPTION">Identifiant unique de l'utilisateur (ou login)&lt;br/&gt;Entre 3 et 64 caractères (lettres, chiffres ou '_')</message>
@@ -222,13 +237,19 @@
     <message key="PLUGINS_CORE_PROFILES_SWITCH_TO_VIEW">Mode visualisation</message>
     <message key="PLUGINS_CORE_PROFILES_SWITCH_TO_VIEW_DESCRIPTION">Passe en mode visualisation des droits du profil</message>
     <message key="PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS">Sauvegarder</message>
-    <message key="PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS_DESCRIPTION">Sauvegarder les modifications effectué sur ce profil.</message>
+    <message key="PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS_DESCRIPTION">Sauvegarder les modifications effectuées sur ce profil.</message>
+    <message key="PLUGINS_CORE_PROFILES_DONT_SAVE_MODIFICATIONS">Annuler</message>
+    <message key="PLUGINS_CORE_PROFILES_DONT_SAVE_MODIFICATIONS_DESCRIPTION">Annuler les modifications en cours du profil.</message>
+    <message key="PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS_ERROR">Une erreur est survenue lors de la sauvegarde des droits du profil.</message>
+    <message key="PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_TITLE">Sauvegarde</message>
+    <message key="PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_DESCRIPTION">Vous avez fait des modifications dans les droits attribués. Souhaitez-vous les sauvegarder ?</message>
     <message key="PLUGINS_CORE_PROFILES_SELECT_ALL">Sélectionner tout</message>
     <message key="PLUGINS_CORE_PROFILES_SELECT_ALL_DESCRIPTION">Sélectionne tous les droits</message>
     <message key="PLUGINS_CORE_PROFILES_UNSELECT_ALL">Désélectionner tout</message>
     <message key="PLUGINS_CORE_PROFILES_UNSELECT_ALL_DESCRIPTION">Désélectionne tous les droits</message>
     <message key="PLUGINS_CORE_PROFILES_DESCRIPTION_NO_SELECTION">Ce bouton est désactivé car aucun profil n'est sélectionné ou le profil sélectionné n'est pas modifiable via cette interface.</message>
     <message key="PLUGINS_CORE_PROFILES_DESCRIPTION_NO_RIGHT">Ce bouton est désactivé car vos droits sont insuffisants.</message>
+    <message key="PLUGINS_CORE_PROFILES_">Ce bouton est désactivé car vos droits sont insuffisants.</message>
     <!-- Helper -->
     <message key="PLUGINS_CORE_PROFILES_DIALOG_ADD_TITLE">Nouveau profil ...</message>
     <message key="PLUGINS_CORE_PROFILES_DIALOG_EDIT_TITLE">Renommer ...</message>
@@ -291,6 +312,17 @@
     <message key="PLUGINS_CORE_RIGHTS_CONTEXTS_APPLICATION_HELP_AFFECT">Pour assigner des droits, ajoutez un utilisateur ou un groupe en cliquant sur les liens ci-dessus, ou sélectionnez directement un groupe ou un utilisateur dans la liste de droite.</message>
     <message key="PLUGINS_CORE_RIGHTS_CONTEXTS_APPLICATION_HELP_PROFIL">Choisissez le ou les profils à appliquer à l'utilisateur ou au groupe sélectionné.&lt;br/&gt;Cliquer sur le lien 'Valider les modifications' pour sauvegarder la nouvelle affectation.</message>
 	
+	<!-- SUPER USER -->
+	<message key="PLUGINS_CORE_SUPERUSER_CATEGORY">Affecter un gestionnaire</message>
+	<message key="PLUGINS_CORE_RIGHTS_SUPERUSER_LABEL">Droit d'affecter un gestionnaire</message>
+	<message key="PLUGINS_CORE_RIGHTS_SUPERUSER_DESCRIPTION">Gestionnaire</message>
+    
+    <message key="PLUGINS_CORE_SUPERUSER_LABEL">Affecter un gestionnaire</message>
+    <message key="PLUGINS_CORE_SUPERUSER_DESCRIPTION">Affecte un gestionnaire</message>
+    <message key="PLUGINS_CORE_SUPERUSER_AFFECT_TITLE">Affecter un gestionnaire</message>
+    <message key="PLUGINS_CORE_SUPERUSER_AFFECT_ERROR">Une erreur est survenue. Impossible d'affecter ce ou ces utilisateurs.</message>
+    <message key="PLUGINS_CORE_SUPERUSER_AFFECT_SUCCESS">L'affectation a été effectuée.</message>
+	
 	<!--  RegExp -->
 	<message key="PLUGINS_CORE_REGEXP_INVALID_MAIL">L'adresse saisie n'est pas une adresse mail valide</message>
 	<message key="PLUGINS_CORE_REGEXP_INVALID_URL">L'url est invalide. Elle doit commencée par http:// ou https://</message>
Index: main/plugin-core/plugin.xml
===================================================================
--- main/plugin-core/plugin.xml	(revision 33953)
+++ main/plugin-core/plugin.xml	(working copy)
@@ -618,6 +618,7 @@
     </feature>
     
     <feature name="core.users">
+
         <components>
             <component class="org.ametys.plugins.core.user.UserDAO" 
                        role="org.ametys.plugins.core.user.UserDAO" 
@@ -632,6 +633,12 @@
                     <description>PLUGINS_CORE_RIGHTS_USERS_DESCRIPTION</description>
                     <category>PLUGINS_CORE_USERANDRIGHTS_CATEGORY</category>
                 </right>
+                
+                <right id="Runtime_Rights_User_Impersonate">
+                    <label>PLUGINS_CORE_RIGHTS_USERS_IMPERSONATE_LABEL</label>
+                    <description>PLUGINS_CORE_RIGHTS_USERS_IMPERSONATE_DESCRIPTION</description>
+                    <category>PLUGINS_CORE_USERANDRIGHTS_CATEGORY</category>
+                </right>
             </extension>
             
             <extension id="org.ametys.core.Users"
@@ -758,6 +765,30 @@
                 <right>Runtime_Rights_User_Handle</right>
             </extension>
             
+            <extension id="org.ametys.core.users.Impersonate"
+                       point="org.ametys.core.ui.RibbonControlsManager"
+                       class="org.ametys.plugins.core.user.ImpersonateUserClientSideElement">
+                <class name="Ametys.ribbon.element.ui.button.OpenToolButtonController">
+                    <action>Ametys.plugins.core.users.UsersActions.impersonate</action>
+
+                    <label i18n="true">PLUGINS_CORE_USERS_IMPERSONATE_LABEL</label>
+                    <description i18n="true">PLUGINS_CORE_USERS_IMPERSONATE_DESCRIPTION</description>
+                    
+                    <selection-target-type>^user$</selection-target-type>
+                    <selection-enable-multiselection>false</selection-enable-multiselection>
+                    <selection-description-multiselectionforbidden i18n="true">PLUGINS_CORE_USERS_IMPERSONATE_MULTI_FORBIDDEN</selection-description-multiselectionforbidden>
+
+                    <help>org.ametys.core.users.rights</help>
+                    <icon-small file="true">img/users/user_16.png</icon-small>
+                    <icon-medium file="true">img/users/user_32.png</icon-medium>
+                    <icon-large file="true">img/users/user_48.png</icon-large>
+                </class>
+                <script>
+                    <file>js/Ametys/plugins/core/users/UsersActions.js</file>
+                </script>
+                <right>Runtime_Rights_User_Impersonate</right>
+            </extension>
+            
             <extension id="org.ametys.core.UserRightsFactory"
                        point="org.ametys.core.ui.UIToolsFactoriesManager"
                        class="org.ametys.core.ui.StaticClientSideElement">
@@ -965,7 +996,7 @@
         <extensions>
             <extension point="org.ametys.core.right.RightsExtensionPoint"
                        id="org.ametys.core.profiles.right">
-                <right id="Runtime_Rights_Profile_Handle">
+                <right id="Runtime_Rights_Rights_Profile_Handle">
                     <label>PLUGINS_CORE_RIGHTS_PROFILES_LABEL</label>
                     <description>PLUGINS_CORE_RIGHTS_PROFILES_DESCRIPTION</description>
                     <category>PLUGINS_CORE_USERANDRIGHTS_CATEGORY</category>
@@ -1011,7 +1042,11 @@
                     <file>js/Ametys/plugins/core/profiles/ProfilesDAO.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesTool.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesTool/Profile.js</file>
+                    <file>js/Ametys/plugins/core/profiles/ProfilesTool/Right.js</file>
                 </scripts>
+                <css>
+                    <file>css/profiles.css</file>
+                </css>
             </extension>
             
             <extension id="org.ametys.core.rights.ProfileTab"
@@ -1082,13 +1117,44 @@
                 <right>Runtime_Rights_Rights_Profile_Handle</right>
             </extension>
             
-            <extension id="org.ametys.core.profiles.SaveRights"
+            <extension id="org.ametys.core.profiles.SwitchMode"
                        point="org.ametys.core.ui.RibbonControlsManager"
                        class="org.ametys.plugins.core.group.ModifiableGroupsClientSideElement">
-                <class name="Ametys.ribbon.element.ui.ButtonController">
-                    <action>Ametys.plugins.core.profiles.ProfilesActions.save</action>
+                <class name="Ametys.plugins.core.profiles.controller.ViewModeController">
+                    <action>Ametys.plugins.core.profiles.ProfilesTool.switchMode</action>
+                    
+                    <enable-toggle>true</enable-toggle>
                     <tool-role>uitool-profile</tool-role>
                     <tool-enable-on-status>focus</tool-enable-on-status>
+                    <selection-target-type>^profile$</selection-target-type>
+                                        
+                    <label i18n="true">PLUGINS_CORE_PROFILES_SWITCH_TO_EDITION</label>
+                    <description i18n="true">PLUGINS_CORE_PROFILES_SWITCH_TO_EDITION_DESCRIPTION</description>
+                    <icon-small file="true">img/profiles/edit_16.png</icon-small>
+                    <icon-medium file="true">img/profiles/edit_32.png</icon-medium>
+                    <icon-large file="true">img/profiles/edit_48.png</icon-large>
+                </class>
+                <scripts>
+                    <file>js/Ametys/plugins/core/profiles/ProfilesDAO.js</file>
+                    <file>js/Ametys/plugins/core/profiles/ProfilesTool.js</file>
+                    <file>js/Ametys/plugins/core/profiles/controller/ViewModeController.js</file>
+                </scripts>
+                <right>Runtime_Rights_Rights_Profile_Handle</right>
+           </extension>
+           
+           <extension id="org.ametys.core.profiles.SaveRights"
+                       point="org.ametys.core.ui.RibbonControlsManager"
+                       class="org.ametys.plugins.core.group.ModifiableGroupsClientSideElement">
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.plugins.core.profiles.ProfilesTool.save</action>
+                   
+                    <disabled>true</disabled>
+                    <selection-target-type>^profile$</selection-target-type>
+                    <selection-subtarget-type>^form$</selection-subtarget-type>
+                    <selection-subtarget-parameter>
+                        <name>isDirty</name>
+                        <value>true</value>
+                    </selection-subtarget-parameter>
                     
                     <label i18n="true">PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS</label>
                     <description i18n="true">PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS_DESCRIPTION</description>
@@ -1103,37 +1169,43 @@
                 <right>Runtime_Rights_Rights_Profile_Handle</right>
             </extension>
             
-            <extension id="org.ametys.core.profiles.SwitchMode"
+            <extension id="org.ametys.core.profiles.Discard"
                        point="org.ametys.core.ui.RibbonControlsManager"
                        class="org.ametys.plugins.core.group.ModifiableGroupsClientSideElement">
-                <class name="Ametys.plugins.core.profiles.controller.ViewModeController">
-                    <action>Ametys.plugins.core.profiles.ProfilesTool.switchMode</action>
-                    <enable-toggle>true</enable-toggle>
-                    <tool-role>uitool-profile</tool-role>
-                    <tool-enable-on-status>focus</tool-enable-on-status>
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.plugins.core.profiles.ProfilesTool.discardChanges</action>
+                   
+                    <disabled>true</disabled>
+                    <selection-target-type>^profile$</selection-target-type>
+                    <selection-subtarget-type>^form$</selection-subtarget-type>
+                    <selection-subtarget-parameter>
+                        <name>isDirty</name>
+                        <value>true</value>
+                    </selection-subtarget-parameter>
                     
-                    <label i18n="true">PLUGINS_CORE_PROFILES_SWITCH_TO_EDITION</label>
-                    <description i18n="true">PLUGINS_CORE_PROFILES_SWITCH_TO_EDITION_DESCRIPTION</description>
-                    <icon-small file="true">img/profiles/edit_16.png</icon-small>
-                    <icon-medium file="true">img/profiles/edit_32.png</icon-medium>
-                    <icon-large file="true">img/profiles/edit_48.png</icon-large>
+                    <label i18n="true">PLUGINS_CORE_PROFILES_DONT_SAVE_MODIFICATIONS</label>
+                    <description i18n="true">PLUGINS_CORE_PROFILES_DONT_SAVE_MODIFICATIONS_DESCRIPTION</description>
+                    <icon-small file="true">img/profiles/save_16.png</icon-small>
+                    <icon-medium file="true">img/profiles/save_32.png</icon-medium>
+                    <icon-large file="true">img/profiles/save_48.png</icon-large>
                 </class>
                 <scripts>
                     <file>js/Ametys/plugins/core/profiles/ProfilesDAO.js</file>
-                    <file>js/Ametys/plugins/core/profiles/ProfilesTool.js</file>
-                    <file>js/Ametys/plugins/core/profiles/controller/ViewModeController.js</file>
+                    <file>js/Ametys/plugins/core/profiles/ProfilesActions.js</file>
                 </scripts>
                 <right>Runtime_Rights_Rights_Profile_Handle</right>
-           </extension>
+            </extension>
            
            <extension id="org.ametys.core.profiles.SelectAll"
                        point="org.ametys.core.ui.RibbonControlsManager"
                        class="org.ametys.plugins.core.group.ModifiableGroupsClientSideElement">
-                <class name="Ametys.ribbon.element.ui.ButtonController">
+                <class name="Ametys.plugins.core.profiles.controller.SelectRightsController">
                     <action>Ametys.plugins.core.profiles.ProfilesTool.selectAll</action>
-                    <tool-role>uitool-profile</tool-role>
-                    <tool-enable-on-status>focus</tool-enable-on-status>
                     
+                    <disabled>true</disabled>
+                    <selection-target-type>^profile$</selection-target-type>
+                    <selection-subtarget-type>^form$</selection-subtarget-type>
+                                        
                     <label i18n="true">PLUGINS_CORE_PROFILES_SELECT_ALL</label>
                     <description i18n="true">PLUGINS_CORE_PROFILES_SELECT_ALL_DESCRIPTION</description>
                     <icon-small file="true">img/profiles/select_16.png</icon-small>
@@ -1141,6 +1213,7 @@
                     <icon-large file="true">img/profiles/select_48.png</icon-large>
                 </class>
                 <scripts>
+                    <file>js/Ametys/plugins/core/profiles/controller/SelectRightsController.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesDAO.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesActions.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesTool.js</file>
@@ -1148,12 +1221,16 @@
                 <right>Runtime_Rights_Rights_Profile_Handle</right>
             </extension>
             
-            <extension id="org.ametys.core.profiles.UnselectAll"
+           <extension id="org.ametys.core.profiles.UnselectAll"
                        point="org.ametys.core.ui.RibbonControlsManager"
                        class="org.ametys.plugins.core.group.ModifiableGroupsClientSideElement">
-                <class name="Ametys.ribbon.element.ui.ButtonController">
+                <class name="Ametys.plugins.core.profiles.controller.SelectRightsController">
                     <action>Ametys.plugins.core.profiles.ProfilesTool.unselectAll</action>
                     
+                    <disabled>true</disabled>
+                    <selection-target-type>^profile$</selection-target-type>
+                    <selection-subtarget-type>^form$</selection-subtarget-type>
+                    
                     <label i18n="true">PLUGINS_CORE_PROFILES_UNSELECT_ALL</label>
                     <description i18n="true">PLUGINS_CORE_PROFILES_UNSELECT_ALL_DESCRIPTION</description>
                     <icon-small file="true">img/profiles/unselect_16.png</icon-small>
@@ -1161,6 +1238,7 @@
                     <icon-large file="true">img/profiles/unselect_48.png</icon-large>
                 </class>
                 <scripts>
+                    <file>js/Ametys/plugins/core/profiles/controller/SelectRightsController.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesDAO.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesActions.js</file>
                     <file>js/Ametys/plugins/core/profiles/ProfilesTool.js</file>
@@ -1170,4 +1248,38 @@
         </extensions>
     </feature>
     
+    <!-- +
+         | SUPER USER
+         + -->
+     <feature name="core.superuser">
+        <extensions>
+            <extension point="org.ametys.core.right.RightsExtensionPoint"
+                       id="org.ametys.core.superuser.right">
+                <right id="Runtime_Rights_Rights_SuperUser_Affect">
+                    <label>PLUGINS_CORE_RIGHTS_SUPERUSER_LABEL</label>
+                    <description>PLUGINS_CORE_RIGHTS_SUPER_DESCRIPTION</description>
+                    <category>PLUGINS_CORE_SUPERUSER_CATEGORY</category>
+                </right>
+            </extension>
+            
+            <extension id="org.ametys.core.superuser.Affect"
+                       point="org.ametys.core.ui.RibbonControlsManager"
+                       class="org.ametys.plugins.core.superuser.SuperUserClientSideElement">
+                <class name="Ametys.ribbon.element.ui.ButtonController">
+                    <action>Ametys.plugins.core.superuser.SuperUserActions.affectSuperUser</action>
+                    
+                    <label i18n="true">PLUGINS_CORE_SUPERUSER_LABEL</label>
+                    <description i18n="true">PLUGINS_CORE_SUPERUSER_DESCRIPTION</description>
+                    
+                    <icon-small file="true">img/superuser/super_user_16.png</icon-small>
+                    <icon-medium file="true">img/superuser/super_user_32.png</icon-medium>
+                    <icon-large file="true">img/superuser/super_user_48.png</icon-large>
+                </class>
+                <scripts>
+                    <file>js/Ametys/plugins/core/superuser/SuperUserActions.js</file>
+                </scripts>
+                <right>Runtime_Rights_Rights_SuperUser_Affect</right>
+            </extension>
+        </extensions>
+     </feature>
 </plugin>
Index: main/plugin-core/resources/css/profiles.css
===================================================================
--- main/plugin-core/resources/css/profiles.css	(revision 0)
+++ main/plugin-core/resources/css/profiles.css	(revision 0)
@@ -0,0 +1,59 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+ 
+.profile-rights-tool .ametys-fieldset
+{
+	background: none;
+}
+
+.profile-rights-tool .ametys-fieldset > .x-panel-body 
+{
+    border: none;
+}
+
+.profile-rights-tool .ametys-fieldset > .x-panel-header
+{
+    background-image: url('../img/profiles/group-expand-sprite.png');
+    background-repeat: no-repeat;   
+    background-position: 0px -45px;
+    background-color: transparent;
+    
+    border: none;
+    box-shadow: none;
+    
+    padding-left: 15px;
+    margin-left: 10px;
+}
+
+.profile-rights-tool .ametys-fieldset > .x-panel-header .x-panel-header-title
+{
+    /*font-family: Arial,sans-serif;*/
+    font-size: 1.1em;
+    color: #404040;
+}
+
+.profile-rights-tool .ametys-fieldset > .x-panel-header.x-panel-header-collapsed-top
+{
+    background-position: 0px 5px;
+}
+
+.profile-rights-tool .ametys-fieldset .right-element .x-component,
+.profile-rights-tool .ametys-fieldset .right-element .x-form-cb-label
+{
+	overflow: hidden;
+	text-overflow: ellipsis;
+	max-width: 280px;
+}
Index: main/plugin-core/resources/js/Ametys/plugins/core/profiles/EditProfileHelper.js
===================================================================
--- main/plugin-core/resources/js/Ametys/plugins/core/profiles/EditProfileHelper.js	(revision 33928)
+++ main/plugin-core/resources/js/Ametys/plugins/core/profiles/EditProfileHelper.js	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2013 Anyware Services
+ *  Copyright 2015 Anyware Services
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
  */
 
 /**
- * Helper for editing a profile
  * @private
+ * Helper for editing a profile
  */
 Ext.define('Ametys.plugins.core.profiles.EditProfileHelper', {
 	singleton: true,
@@ -32,9 +32,9 @@
 	
 	/**
 	 * Open dialog box to create a new profile
-	 *  @param {String} context The context. Can be null.
+	 * @param {String} context The context. Can be null.
 	 * @param {Function} [callback] the callback function. Parameters are:
-	 * @param {Object} login callback.profile The profile's properties
+	 * @param {Object}  callback.profile The profile's properties
 	 */
 	add: function (context, callback)
 	{
Index: main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesActions.js
===================================================================
--- main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesActions.js	(revision 33858)
+++ main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesActions.js	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2013 Anyware Services
+ *  Copyright 2015 Anyware Services
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
  */
 
 /**
- * Singleton class defining the actions related to groups.
  * @private
+ * Singleton class defining the actions related to groups.
  */
 Ext.define('Ametys.plugins.core.profiles.ProfilesActions', {
 	singleton: true,
@@ -62,11 +62,11 @@
 	},
 
 	/**
+	 * @private
 	 * Callback function invoked after the 'delete' confirm box is closed
 	 * @param {String} buttonId Id of the button that was clicked
 	 * @param {Ametys.message.MessageTarget[]} targets The profiles message targets
 	 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function 
-	 * @private
 	 */
 	_doDelete: function(buttonId, targets, controller)
 	{
Index: main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesDAO.js
===================================================================
--- main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesDAO.js	(revision 33858)
+++ main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesDAO.js	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2013 Anyware Services
+ *  Copyright 2015 Anyware Services
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -118,7 +118,7 @@
 	     		},
 	     		errorMessage: {
 					category: this.self.getName(),
-					msg: "<i18n:text i18n:key='PLUGINS_CORE_PROFILES_EDIT_ERROR'/>"
+					msg: "<i18n:text i18n:key='PLUGINS_CORE_PROFILES_SAVE_MODIFICATIONS_ERROR'/>"
 				}
 			});
 			
@@ -230,7 +230,7 @@
 		
 		/**
 		 * @private
-		 * Callback function called after a profile was edited
+		 * Callback function called after the rights associated to a profile were edited
 		 * @param {Object} profile The profile's information
 		 * @param {Object} args The callback arguments
 		 * @param {Object[]} params The callback parameters (server-side and client-side)
Index: main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesTool.js
===================================================================
--- main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesTool.js	(revision 33858)
+++ main/plugin-core/resources/js/Ametys/plugins/core/profiles/ProfilesTool.js	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2013 Anyware Services
+ *  Copyright 2015 Anyware Services
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -15,13 +15,65 @@
  */
 
 /**
- * This tool displays the list of users
  * @private
+ * This tool displays the list of users
  */
 Ext.define('Ametys.plugins.core.profiles.ProfilesTool', {
 	extend: 'Ametys.tool.Tool',
 	
+	/**
+	 * @private
+	 * @property {Ext.grid.Panel} _profilesGrid the profiles' grid panel  
+	 */
+	
+	/**
+	 * @private
+	 * @property {Ext.panel.Panel} _rightsPanel the rights' panel
+	 */
+	
+	/**
+	 * @private
+	 * @property {Object} _rightsByCategory the right records sorted by category name
+	 */
+	
+	/**
+	 * @private
+	 * @property {Ext.data.Record} _selection the selected profile record
+	 */
+	
+	/**
+	 * @private
+	 * @property {String[]} _rights the list of rights before the tool gets dirty
+	 */
+	
 	statics: {
+		
+		/**
+		 * Save the rights for the selected profile
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function 
+		 */
+		save: function(controller)
+		{
+			var profileTool = Ametys.tool.ToolsManager.getFocusedTool();
+			var rights = profileTool._getRights();
+			
+			profileTool.saveRights(profileTool._selection.id, rights, profileTool._updateCardPanels(rights));
+			profileTool.sendCurrentSelection();
+		},
+		
+		/**
+		 * Discard the pending changes on the selected profile
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function 
+		 */
+		discardChanges: function(controller)
+		{
+			var profileTool = Ametys.tool.ToolsManager.getFocusedTool();
+			
+			profileTool._updateCardPanels(profileTool._rights);
+			profileTool.setDirty(false);
+			profileTool.sendCurrentSelection();
+		},
+
 		/**
 		 * Switch mode
 		 * @param {String} mode The mode: 'view' or 'edit'
@@ -34,6 +86,7 @@
 		
 		/**
 		 * Select all rights
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function 
 		 */
 		selectAll: function (controller)
 		{
@@ -43,6 +96,7 @@
 		
 		/**
 		 * Unselect all rights
+		 * @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function 
 		 */
 		unselectAll: function (controller)
 		{
@@ -66,61 +120,142 @@
 		return Ametys.tool.Tool.MB_TYPE_ACTIVE;
 	},
 	
-	setParams: function(params)
-	{
+    setParams: function (params)
+    {
 		this.callParent(arguments);
 		this.refresh();
-	},
+    },
 	
 	createPanel: function()
 	{
 		var profileStore = this._createProfileStore();
+		var rightsStore = this._createRightsStore();
 		
-		this._profileGrid = Ext.create('Ext.grid.Panel', {
-			region: 'west',
-			border: false,
-			autoScroll: true,
-			width: 500,
+		this._profilesGrid = Ext.create('Ext.grid.Panel', {
+			border: true,
+			scrollable: 'vertical',
+			
+			stateful: true,
+			stateId: this.self.getName() + "$grid",
+			
+			minWidth: 100,
+			maxWidth: 250,
+			flex: 0.3,
 			
 			store: profileStore,
-			columns: [{flex: 1, header: "<i18n:text i18n:key='PLUGINS_CORE_UITOOL_PROFILES_LABEL' i18n:catalogue='plugin.core'/>", width: 330, dataIndex: 'label', renderer: this._renderProfilName, hideable: false}],
+			columns: [{flex: 1, header: "<i18n:text i18n:key='PLUGINS_CORE_UITOOL_PROFILES_LABEL' i18n:catalogue='plugin.core'/>", dataIndex: 'label', renderer: this._renderProfilName, hideable: false}],
 			
 			listeners: {
-				selectionchange: {fn: this._onProfileSelectionChange, scope: this}
+				beforeselect: {fn: this._onBeforeSelectProfile, scope: this}
 			}
 		});
 		
-		this._rightsPanel = Ext.create('Ext.panel.Panel', {
-			region: 'center',
+		this._rightsPanel = Ext.create('Ext.container.Container', {
+			flex: 1,
+			minWidth: 350,
+			split: true,
 			layout: 'card',
-			activeItem:0,
+			cls: 'profile-rights-tool',
+			activeItem: 0,
 			
+			store: rightsStore,
 			items: [{
 				itemId: 'card-view',
-				autoScroll:true,
+				scrollable: 'vertical',
 				border: false,
-				html: 'TODO Mode visualisation'
+				items: []
 			}, {
 				itemId: 'card-edit',
-				autoScroll:true,
+				scrollable: true,
 				border: false,
-				html: 'TODO Mode edition'
+				items: []
 			}]
 		});
 		
-		return Ext.create('Ext.panel.Panel', {
-			defaults: {
-				split: true
+		return Ext.create('Ext.container.Container', {
+			layout: {
+				type: 'hbox',
+				pack: 'start',
+				align: 'stretch'
 			},
-			layout: 'border',
-			items: [this._profileGrid, this._rightsPanel]
+			scrollable: 'horizontal',
+			items: [this._profilesGrid, this._rightsPanel]
+		});
+	},
+	
+	sendCurrentSelection: function()
+	{
+		var targets = [];
+		var subtargets = [];
+		var profileId = null;
+		
+		if (this._selection != null)
+		{
+			profileId = this._selection.id;
+		}
+		
+		var mode = this.getMode();
+		if (mode == 'edit')
+		{
+			var isDirty = this.isDirty();
+			subtargets.push({
+	            'type': Ametys.message.MessageTarget.FORM,
+	            
+	            'parameters': {
+	                'isDirty': isDirty
+	            }
+	        });
+		}
+		
+		targets.push ({
+			type: Ametys.message.MessageTarget.PROFILE,
+			parameters: {id: profileId},
+			subtargets: subtargets
+		});
+		
+		Ext.create('Ametys.message.Message', {
+			type: Ametys.message.Message.SELECTION_CHANGED,
+			targets: targets
 		});
 	},
 	
+	close: function(manual)
+	{
+        if (this.isDirty())
+		{
+        	var me = this;
+        	var rights = this._getRights();
+        	
+        	var callback = function (doSave)
+        	{
+        		if (doSave == null)
+    			{
+        			return;
+    			}
+        		else
+    			{
+        			if (doSave)
+    				{
+        				me.saveRights(me._selectedProfile, rights, function() {Ametys.plugins.core.profiles.ProfilesTool.superclass.close.call(me, [manual])});
+    				}
+        			else
+    				{
+        				Ametys.plugins.core.profiles.ProfilesTool.superclass.close.call(me, [manual]);
+    				}
+    			}
+        	}
+        	
+        	Ametys.form.SaveHelper.promptBeforeQuit(null, "<i18n:text i18n:key='PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_DESCRIPTION'/>", null, callback);
+        	return;
+		}
+
+        this.callParent(arguments);
+	},
+	
 	/**
 	 * @private
 	 * Create the profiles' store
-	 * @return {Ext.data.Store} The store
+	 * @return {Ext.data.Store} The profiles' store
 	 */
 	_createProfileStore: function ()
 	{
@@ -145,9 +280,38 @@
 		return Ext.create('Ext.data.Store', storeConfig);
 	},
 	
+	
+	/**
+	 * @private
+	 * Create the rights' store
+	 * @return {Ext.data.Store} the rights' store
+	 */
+	_createRightsStore: function()
+	{
+		var storeConfig = Ext.merge({
+			autoLoad: true,
+			
+			model: 'Ametys.plugins.core.profiles.ProfilesTool.Right',
+			proxy: {
+				type: 'ametys',
+				reader: {
+					type: 'json',
+					rootProperty: 'rights'
+				}
+			},
+			
+			remoteSort: false,
+			sortOnLoad: true,
+			sorters: [{property: 'category', direction:'ASC'}]
+			
+		}, this.getRightsStoreConfig());
+		
+		return Ext.create('Ext.data.Store', storeConfig);
+	},
+	
 	/**
-	 * Returns the elements of configuration of group store to be overridden.
-	 * Override this function if you want to override the group store configuration.
+	 * Returns the elements of configuration of profiles store to be overridden.
+	 * Override this function if you want to override the profiles store configuration.
 	 * @return {Object} The elements of store configuration to be overridden
 	 */
 	getProfileStoreConfig: function()
@@ -165,16 +329,336 @@
 	},
 	
 	/**
-	 * Listener when a profile is selected. Update the rights' panel
-	 * @param {Ext.selection.Model} model The selection model
-	 * @param {Ext.data.Model[]} selected The selected records
+	 * Returns the elements of configuration of rights store to be overridden.
+	 * Override this function if you want to override the rights store configuration.
+	 * @return {Object} The elements of store configuration to be overridden
+	 */
+	getRightsStoreConfig: function()
+	{
+		return {
+			proxy: {
+				plugin: 'core',
+				url: 'rights/rights.json'
+			},
+			
+			listeners: {
+				load: {fn: this._onRightsStoreLoaded, scope: this}
+			} 
+		};
+	},
+	
+	/**
 	 * @private
+	 * Function invoked before a profile is selected. Update the rights' panel
+	 * @param {Ext.selection.Model} model The selection model
+	 * @param {Ext.data.Model} selection The selected record
 	 */
-	_onProfileSelectionChange: function(model, selected)
+	_onBeforeSelectProfile: function(model, selection)
 	{
-		// TODO Update rights panel
+		if (this._selection != null && this.isDirty())
+		{
+			var me = this; 
+			var rights = this._getRights();
+			var callback = function (doSave)
+			{
+				if (doSave != null)
+				{
+	    			doSave ? me.saveRights(me._selection.id, rights) : me.setDirty(false);  
+	    			me._profilesGrid.setSelection(selection);
+				}
+			}
+			
+			Ametys.form.SaveHelper.promptBeforeQuit(null, "<i18n:text i18n:key='PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_DESCRIPTION'/>", null, callback);
+
+			// Cancel the selection, it will be done after the user answered the dialog
+			return false;
+    	}
 		
+		this._selection = selection;
+
 		this.sendCurrentSelection();
+		this._updateCardPanels(selection.get('rights') || []);
+	},
+	
+	/**
+	 * @private
+	 * Listener invoked when the rights store is loaded
+	 * @param {Ext.data.Store} store the rights store
+	 * @param {Ext.data.Record[]} records the records of the store
+	 */
+	_onRightsStoreLoaded: function(store, records)
+	{
+		this._rightsByCategory = {};
+		
+		// Categorize the rights
+		// We generate the id 'categoryId' that will be used later in order to avoid adding it in the server method right2Json
+		// Indeed, we can't use the category name directly because some of their characters may not be accepted within an itemId
+		
+		var categoriesIds = {};
+		var categoryId = null; 
+		for (var i = 0; i < store.getTotalCount(); i++)
+		{
+			// work on store data instead of the records because the records are not sorted
+			var record = store.getAt(i);
+			
+			var categoryName = record.get('category');
+			categoryId = categoriesIds[categoryName];
+			if (categoryId == null)
+			{
+				// Create a new identifier every time we encounter an unregistered category name
+				categoryId = Ext.id();
+				categoriesIds[categoryName] = categoryId;
+				
+				this._rightsByCategory[categoryId] = [];
+			}
+			
+			this._rightsByCategory[categoryId].push(record);
+		}
+		this._drawCardPanels();
+	},
+	
+	/**
+	 * @private
+	 * Draw items inside both of the panels of the card panel of the tool and hide them
+	 */ 
+	_drawCardPanels: function()
+	{
+		var me = this;
+		
+		var cardViewPanel = this._rightsPanel.getComponent('card-view');
+		var cardEditPanel = this._rightsPanel.getComponent('card-edit');
+		
+		Ext.suspendLayouts();
+		
+		Ext.Object.each(this._rightsByCategory, function(categoryId) {
+
+			var categoryRights = this._rightsByCategory[categoryId];
+			
+			// Common configuration
+			var categoryPanelCfg = {
+				itemId: categoryId,
+					
+				title : categoryRights[0].get('category'),
+	            layout: 'column',
+	            cls: 'ametys-fieldset',
+ 				
+				collapsible: true,
+	            titleCollapse: true,
+	            hideCollapseTool: true,
+	            
+	            border: false
+			};
+			
+			var categoryViewPanel = Ext.create('Ext.panel.Panel', categoryPanelCfg);
+			var categoryEditPanel = Ext.create('Ext.panel.Panel', Ext.applyIf(
+			{
+				tools: [
+			        {
+			        	xtype: 'button',
+			        	itemId: 'category-select-all',
+			        	icon: Ametys.getPluginResourcesPrefix('core') + '/img/profiles/select_16.png',
+			        	tooltip: '<i18n:text i18n:key="PLUGINS_CORE_UITOOL_PROFILES_SELECT_ALL"/>',
+			        	border: false,
+                        handler: function() {me.selectAll(categoryId)},
+                        listeners: 
+						{
+						    click: 
+						    {
+						        fn: function(header, event) 
+						        {
+						    	  // Prevent panel collapsing mechanism
+						    	  event.stopPropagation();
+						        }
+						    }
+						}
+                        
+			        },
+			        {
+			        	xtype: 'button',
+			        	itemId: 'category-unselect-all',
+			        	icon: Ametys.getPluginResourcesPrefix('core') + '/img/profiles/unselect_16.png',
+			        	tooltip: '<i18n:text i18n:key="PLUGINS_CORE_UITOOL_PROFILES_UNSELECT_ALL"/>',
+			        	border: false,
+			        	handler: function() {me.unselectAll(categoryId)},
+			        	listeners: 
+			        	{
+						    click: 
+						    {
+						        fn: function(header, event) 
+						        {
+						    	  // Prevent panel collapsing mechanism
+						    	  event.stopPropagation();
+						        }
+						    } 
+						}
+			        }
+		        ]
+			}, categoryPanelCfg));
+			
+			Ext.Array.each(categoryRights, function(right) {
+				
+  				var rightId = right.get('id'),
+				    rightLabel = right.get('label'),
+				    rightDescription = right.get('description');
+				
+  				// common configuration
+				var rightContainerConfig = 
+				{
+					columnWidth: 0.33,
+					padding: 5,
+					
+					cls: 'right-element',
+					
+					layout: {
+						type: 'hbox',
+						align: 'middle'
+					}
+				};
+				
+				// Visualization container for a right
+				var viewRightContainer = Ext.create('Ext.Container', 
+					Ext.applyIf(
+					{
+						itemId: rightId + '-view',
+						
+						items: [
+				        {
+					        xtype: 'component',
+					        cls: 'ametys-description',
+					        width: 20,
+					        
+					        margin: '-1 0 0 0',
+
+							listeners: {
+								render: function() {
+									this.getEl().set({'data-qtip': rightDescription, 'data-qtitle': rightLabel});
+								}
+							}
+				        }]
+					
+					}, rightContainerConfig)
+				);
+				
+				// add the text
+				viewRightContainer.add({
+					xtype: 'component',
+					html: rightLabel
+				});
+
+				// Edition container for a right
+				var editRightContainer = Ext.create('Ext.form.field.Checkbox', 
+					Ext.applyIf(
+					{
+						itemId: rightId + '-edit',
+						
+						boxLabel: rightLabel, // text for the component that will be created
+						disabled: true,
+						
+						listeners: {
+							change: Ext.bind(me._onEditRightFieldChange, me),
+							render: function() 
+							{
+								this.getEl().insertFirst({
+									cls: 'ametys-description',
+							        width: 20,
+							        
+							        margin: '-1 0 0 0',
+									'data-qtitle': rightLabel,
+									'data-qtip': rightDescription
+								});
+							}
+						}
+					}, rightContainerConfig)
+				);
+				
+				categoryViewPanel.add(viewRightContainer);
+				categoryEditPanel.add(editRightContainer);
+			});
+			
+			cardViewPanel.add(categoryViewPanel);
+			cardEditPanel.add(categoryEditPanel);
+			
+			// The category panels are initially hidden for visualization mode
+			categoryViewPanel.hide();
+
+		}, this);
+		
+		Ext.resumeLayouts(true);
+	},
+	
+	/**
+	 * @private
+	 * Handler function invoked when an right edition field has changed
+	 * @param {Ext.form.field.Checkbox} box the checkbox
+	 * @param {Object} newValue the value of the field after it has changed
+	 * @param {Object} oldValue the value of the field before it has changed
+	 */
+	_onEditRightFieldChange: function(box, newValue, oldValue) 
+	{
+		var focusedTool = Ametys.tool.ToolsManager.getFocusedTool();
+        if (focusedTool != null && !focusedTool.isDirty())
+    	{
+        	focusedTool.setDirty(true);
+        	this.sendCurrentSelection();
+    	}
+	},
+	
+	/**
+	 * @private
+	 * Update the card panels according to the selected profile's right ids
+	 * @param {String[]} profileRightsIds the ids of the rights for the selected profile
+	 */
+	_updateCardPanels: function(profileRightsIds)
+	{
+		Ext.suspendLayouts();
+		
+		// Keep a copy of the rights before the tool is dirty
+		var categoriesToShow = [];
+		var cardViewPanel = this._rightsPanel.getComponent('card-view');
+		var cardEditPanel = this._rightsPanel.getComponent('card-edit');
+		
+		Ext.Object.each(this._rightsByCategory, function(categoryId) {
+			
+			var rights = this._rightsByCategory[categoryId];
+			
+			var categoryViewPanel = cardViewPanel.getComponent(categoryId);
+			var categoryEditPanel = cardEditPanel.getComponent(categoryId);
+			
+			Ext.Array.each(rights, function (right)
+			{
+				var rightId = right.get('id');
+
+				var hasRight = Ext.Array.contains(profileRightsIds, rightId);
+				
+				// Update edition screen without triggering a change event
+				var editRightContainer = categoryEditPanel.getComponent(rightId + '-edit');
+				editRightContainer.suspendEvents(false);
+				editRightContainer.setValue(hasRight);
+				editRightContainer.enable();
+				editRightContainer.resumeEvents();
+				
+				// Update view screen
+				var viewRightContainer = categoryViewPanel.getComponent(rightId + '-view');
+				viewRightContainer.setVisible(hasRight);
+				
+				// Update categories
+				if (Ext.Array.contains(profileRightsIds, rightId) && !Ext.Array.contains(categoriesToShow, categoryId))
+				{
+					categoriesToShow.push(categoryId);
+				}
+			}, this);
+			
+			categoryEditPanel.show();
+		}, this);
+
+		// Update category panels for visualization mode
+		Ext.Object.each(this._rightsByCategory, function(categoryId) {
+			cardViewPanel.getComponent(categoryId).setVisible(Ext.Array.contains(categoriesToShow, categoryId));
+		}, this);
+		
+		this._rights = this._getRights();
+		
+		Ext.resumeLayouts(true);
 	},
 	
 	/**
@@ -187,38 +671,132 @@
 		{
 			// Go to edition mode
 			this._rightsPanel.getLayout().setActiveItem(1);
+			this.sendCurrentSelection();
 		}
 		else
-		{
-			// Go to view mode
-			this._rightsPanel.getLayout().setActiveItem(0);
-			// TODO Update rights
-		}
-		
-		Ext.create('Ametys.message.Message', {
-			type: Ametys.message.Message.TOOL_PARAMS_UPDATED, // TODO TOOL_STATE_CHANGED ??
-			targets: {
-				type: Ametys.message.MessageTarget.TOOL,
-				parameters: {'tools': [this]}
+		{	
+			if (this.isDirty())
+			{
+				var me = this;
+				var rights = this._getRights();
+			
+				var callback = function(doSave) 
+				{
+					if (doSave == null)
+					{
+						return;
+					}
+					else
+					{
+						if (doSave)
+						{
+							me.saveRights(me._selection.id, rights, function()
+								{
+									me._updateCardPanels(rights); 
+									me._rightsPanel.getLayout().setActiveItem(0);
+									me.sendCurrentSelection();
+								}
+							);
+						}
+						else
+						{
+							me.setDirty(false);
+							me._updateCardPanels(me._rights);
+							me._rightsPanel.getLayout().setActiveItem(0);
+							me.sendCurrentSelection();
+						}
+					}
+				}
+				
+				Ametys.form.SaveHelper.promptBeforeQuit(null, "<i18n:text i18n:key='PLUGINS_CORE_PROFILES_RIGHTS_CONFIRM_DESCRIPTION'/>", null, callback);
 			}
-		});
+			else
+			{
+				// Go to view mode
+				this._rightsPanel.getLayout().setActiveItem(0);
+				this.sendCurrentSelection();
+			}
+		}
 	},
 	
+	/**
+	 * @private
+	 * Get the selected rights
+	 */
+	_getRights: function()
+	{
+		var rights = [];
+		
+		// Gather the selected rights
+		var cardEditPanel = this._rightsPanel.getComponent('card-edit');
+		Ext.Object.each(this._rightsByCategory, function(categoryId) 
+		{
+			var categoryEditPanel = cardEditPanel.getComponent(categoryId);
+			
+			var categoryRights = this._rightsByCategory[categoryId];
+			Ext.Array.each(categoryRights, function (right)
+			{
+				var rightId = right.get('id');
+				var editRightContainer = categoryEditPanel.getComponent(rightId + '-edit');
+				
+				if (editRightContainer.getValue())
+				{
+					rights.push(rightId);
+				}
+				
+			}, this);
+		}, this);
+		
+		return rights;
+	},
 	
 	/**
+	 * Actual saving process with a server call
+	 * @param {String} selectedProfile the id of the selected profile
+	 * @param {String[]} rights the selected rights for the selected profile
+	 * @param {Function} callback the callback function
+	 */
+	saveRights: function(selectedProfile, newRights, callback)
+	{
+		Ametys.plugins.core.profiles.ProfilesDAO.editProfileRights([selectedProfile, newRights], callback, {});
+
+		// Reset the dirty state of the tool
+		this.setDirty(false);
+	},	
+
+	/**
 	 * Select all rights
+	 * @param {String} categoryId The item id of the html element surrounding the elements to check. Can be null to check all.
+	 * @param {Boolean} [value=true] The value to set to all rights: true means rights are set and false unset.
 	 */
-	selectAll: function ()
+	selectAll: function(categoryId, value)
 	{
-		alert("Method #selectAll is not yet implemented");
+		var cardEditPanel = this._rightsPanel.getComponent('card-edit');
+		
+		value = value == null ? true : value;
+		if (categoryId != null)
+		{
+			var categoryEditPanel = cardEditPanel.getComponent(categoryId);
+			
+			categoryEditPanel.items.each (function (item, index) {item.setValue(value)});
+		}
+		else
+		{
+			cardEditPanel.items.each(function(item, index) {
+				item.items.each(function(item, index) {
+					item.setValue(value);
+				});
+			});
+		}
 	},
 	
 	/**
 	 * Unselect all rights
+	 * @param {String} categoryId The item id of the html element surrounding the elements to uncheck. Can be null to uncheck all.
 	 */
-	unselectAll: function ()
+	unselectAll: function(categoryId)
 	{
-		alert("Method #unselectAll is not yet implemented");
+		this.selectAll(categoryId, false);
 	},
 	
 	/**
@@ -230,28 +808,10 @@
 		return this._rightsPanel.getLayout().getActiveItem().getItemId() == 'card-view' ? 'view' : 'edit';
 	},
 	
-	sendCurrentSelection: function()
-	{
-		var targets = [];
-		var selection = this._profileGrid.getSelectionModel().getSelection();
-		
-		Ext.Array.forEach (selection, function (profile) {
-			targets.push ({
-				type: Ametys.message.MessageTarget.PROFILE,
-				parameters: {id: profile.getId()}
-			})
-		});
-		
-		Ext.create('Ametys.message.Message', {
-			type: Ametys.message.Message.SELECTION_CHANGED,
-			targets: targets
-		});
-	},
-	
 	/**
+	 * @private
 	 * Listener when a Ametys.message.Message#CREATED message was received
 	 * @param {Ametys.message.Message} message The received message
-	 * @private
 	 */
 	_onMessageCreated: function(message)
 	{
@@ -259,7 +819,7 @@
 		if (target)
 		{
 			var id = target.getParameters().id;
-			var profile = this._profileGrid.getStore().getById(id);
+			var profile = this._profilesGrid.getStore().getById(id);
 			if (profile)
 			{
 				Ametys.plugins.core.profiles.ProfilesDAO.getProfile([id], this._updateProfile, {scope: this});
@@ -272,9 +832,9 @@
 	},
 	
 	/**
-	 * Listener when a Ametys.message.Message#MODIFIED message was received
-	 * @param {Ametys.message.Message} message The received message
 	 * @private
+	 * Listener when a Ametys.message.Message#EDITED message was received
+	 * @param {Ametys.message.Message} message The received message
 	 */
 	_onMessageEdited: function(message)
 	{
@@ -288,22 +848,21 @@
 			}
 			else
 			{
-				// TODO reload rights
-				
+				this._profilesGrid.getStore().reload();
 			}
 		}
 	},
 	
 	/**
+	 * @private
 	 * Listener when a Ametys.message.Message#DELETED message was received
 	 * @param {Ametys.message.Message} message The received message
-	 * @private
 	 */
 	_onMessageDeleted: function(message)
 	{
 		var targets = message.getTargets(Ametys.message.MessageTarget.PROFILE);
 		
-		var store = this._profileGrid.getStore();
+		var store = this._profilesGrid.getStore();
 		Ext.Array.forEach(targets, function(target) {
 			var profile = store.getById(target.getParameters().id);
 			if (profile)
@@ -323,8 +882,8 @@
 		if (profile && profile.id)
 		{
 			var record = Ext.create('Ametys.plugins.core.profiles.ProfilesTool.Profile', profile);
-			this._profileGrid.getStore().addSorted(record);
-			this._profileGrid.getSelectionModel().select([record]);
+			this._profilesGrid.getStore().addSorted(record);
+			this._profilesGrid.getSelectionModel().select([record]);
 		}
 	},
 	
@@ -337,7 +896,7 @@
 	{
 		if (profile && profile.id)
 		{
-			var store = this._profileGrid.getStore();
+			var store = this._profilesGrid.getStore();
 			var record = store.getById(profile.id);
 			
 			record.beginEdit();
@@ -351,6 +910,4 @@
 			store.sort();
 		}
 	}
-	
-
 });
Index: main/plugin-core/resources/js/Ametys/plugins/core/profiles/controller/SelectRightsController.js
===================================================================
--- main/plugin-core/resources/js/Ametys/plugins/core/profiles/controller/SelectRightsController.js	(revision 0)
+++ main/plugin-core/resources/js/Ametys/plugins/core/profiles/controller/SelectRightsController.js	(revision 0)
@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2015 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * @private
+ * This class controls a ribbon button representing the lock state of a content
+ */
+Ext.define('Ametys.plugins.core.profiles.controller.SelectRightsController', {
+	extend: 'Ametys.ribbon.element.ui.ButtonController',
+
+	updateState: function()
+	{
+		var targets = this.getMatchingTargets();
+		if (targets.length > 0)
+		{
+			var profileTarget = targets[0];
+			this.setDisabled(profileTarget.getParameters().id == null);
+		}
+	}
+});
Index: main/plugin-core/resources/js/Ametys/plugins/core/profiles/controller/ViewModeController.js
===================================================================
--- main/plugin-core/resources/js/Ametys/plugins/core/profiles/controller/ViewModeController.js	(revision 33858)
+++ main/plugin-core/resources/js/Ametys/plugins/core/profiles/controller/ViewModeController.js	(working copy)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2013 Anyware Services
+ *  Copyright 2015 Anyware Services
  *
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -15,34 +15,29 @@
  */
 
 /**
- * This class controls a ribbon button representing the lock state of a content
  * @private
+ * This class controls a ribbon button representing the lock state of a content
  */
 Ext.define('Ametys.plugins.core.profiles.controller.ViewModeController', {
 	extend: 'Ametys.ribbon.element.ui.ButtonController',
-	
-	constructor: function(config)
+
+	areSameTargets: function(target1, target2)
 	{
-		this.callParent(arguments);
-		
-		Ametys.message.MessageBus.on(Ametys.message.Message.TOOL_PARAMS_UPDATED, this._onToolParamsUpdated, this);
+		var areSameTargets = this.callParent(arguments);
+		return areSameTargets && target1.getSubtarget(Ametys.message.MessageTarget.FORM) == target2.getSubtarget(Ametys.message.MessageTarget.FORM);
 	},
-
-	/**
-	 * Listener when the tool's params has been updated
-	 * Will update the state of the buttons effectively upon the current selection.
-	 * @param {Ametys.message.Message} message The updated message.
-	 * @protected
-	 */
-	_onToolParamsUpdated: function (message)
+	
+	updateState: function()
 	{
-		var toolTarget = message.getTarget(Ametys.message.MessageTarget.TOOL);
-		if (toolTarget && Ext.getClassName(toolTarget.getParameters().tool) == 'Ametys.plugins.core.profiles.ProfilesTool')
+		this.enable();
+		
+		var targets = this.getMatchingTargets();
+		if (targets.length > 0)
 		{
-			var tool = toolTarget.getParameters().tool;
-			var mode = tool.getMode();
-			this.toggle(mode == 'edit');
+			var profileTarget = targets[0];
+			var formTarget = profileTarget.getSubtarget(Ametys.message.MessageTarget.FORM);
+			
+			this.toggle(formTarget != null);
 		}
 	}
-	
 });
