[FIX] project: access rights and followers
authorMartin Trigaux <mat@odoo.com>
Tue, 18 Nov 2014 14:09:13 +0000 (15:09 +0100)
committerMartin Trigaux <mat@odoo.com>
Tue, 18 Nov 2014 17:52:46 +0000 (18:52 +0100)
For privacy_visibility 'followers' or 'portal', the user should be follower of the project (not the task).
Remove public access to portal task
Fixes #2372

If no project on the task (or other rule), an employee (not a portal) can access if is follower of the task.
Follower rule is not enough as a user creating a rule will subscribe to the rule but to subscribe to record, the user should have access to it in the first place.
To make sure the snake does not bit its tail, fallback to give access on task where the user is reponsible (user_id = user.id).
Fixes #139

Adapted the tests to the new behaviour (removed not relevant and added some on creation)

addons/portal_project/security/portal_security.xml
addons/portal_project/tests/test_access_rights.py
addons/project/security/project_security.xml

index f6adf6b..b35c8be 100644 (file)
         <record id="portal_project_rule" model="ir.rule">
             <field name="name">Project: portal users: public, portal or following</field>
             <field name="model_id" ref="project.model_project_project"/>
-            <field name="domain_force">['|',
-                                            ('privacy_visibility', 'in', ['public', 'portal']),
-                                            '&amp;',
-                                                ('privacy_visibility', '=', 'followers'),
-                                                ('message_follower_ids', 'in', [user.partner_id.id]),
-                                            ]</field>
+            <field name="domain_force">[
+                '|',
+                    '|',
+                        ('privacy_visibility', '=', 'public'),
+                        '&amp;',
+                            ('privacy_visibility', '=', 'portal'),
+                            ('message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+                    '&amp;',
+                        ('privacy_visibility', '=', 'followers'),
+                        ('message_follower_ids', 'in', [user.partner_id.id])
+            ]</field>
             <field name="groups" eval="[(4, ref('base.group_portal'))]"/>
         </record>
 
 
         <record model="ir.rule" id="project.task_visibility_rule">
             <field name="name">Project/Task: employees: public, portal, employee or (followers and following)</field>
-            <field name="domain_force">['|',
-                                            ('project_id.privacy_visibility', 'in', ['public', 'portal', 'employees']),
-                                            '&amp;',
-                                                ('project_id.privacy_visibility', '=', 'followers'),
-                                                ('message_follower_ids', 'in', [user.partner_id.id]),
-                                        ]</field>
+            <field name="domain_force">[
+            '|',
+                ('project_id.privacy_visibility', 'in', ['public', 'portal', 'employees']),
+                '|',
+                    '&amp;',
+                        ('project_id.privacy_visibility', '=', 'followers'),
+                        ('project_id.message_follower_ids', 'in', [user.partner_id.id]),
+                    '|',
+                        ('message_follower_ids', 'in', [user.partner_id.id]),
+                        ('user_id', '=', user.id)
+            ]</field>
         </record>
 
         <record id="portal_task_rule" model="ir.rule">
             <field name="name">Project/Task: portal users: public or (portal and colleagues following) or (followers and following)</field>
             <field name="model_id" ref="project.model_project_task"/>
-            <field name="domain_force">['|', '|',
-                                            ('project_id.privacy_visibility', 'in', ['public']),
-                                            '&amp;',
-                                                ('project_id.privacy_visibility', '=', 'portal'),
-                                                ('message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
-                                            '&amp;',
-                                                ('project_id.privacy_visibility', '=', 'followers'),
-                                                ('message_follower_ids', 'in', [user.partner_id.id]),
-                                        ]</field>
+            <field name="domain_force">[
+            '|',
+                '|',
+                    '|',
+                        ('project_id.privacy_visibility', '=', 'public'),
+                        '&amp;',
+                            ('project_id.privacy_visibility', '=', 'portal'),
+                            ('project_id.message_follower_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
+                    '&amp;',
+                        ('project_id.privacy_visibility', '=', 'followers'),
+                        ('project_id.message_follower_ids', 'in', [user.partner_id.id]),
+                '&amp;',
+                    # on employee project can receive messages but not access the object
+                    ('project_id.privacy_visibility', '!=', 'employees'),
+                    ('message_follower_ids', 'in', [user.partner_id.id])
+            ]</field>
             <field name="groups" eval="[(4, ref('base.group_portal'))]"/>
         </record>
 
index e36df90..a4f2973 100644 (file)
@@ -149,15 +149,11 @@ class TestPortalProject(TestPortalProjectBase):
         self.assertRaises(AccessError, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
 
         # Data: task follower
+        self.project_project.message_subscribe_users(cr, self.user_manager_id, [pigs_id], [self.user_portal_id])
         self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
 
-        # Do: Chell reads project -> ok (portal ok public)
+        # Do: Chell reads project -> ok (portal ok portal)
         self.project_project.read(cr, self.user_portal_id, [pigs_id], ['state'])
-        # Test: only followed project tasks visible + assigned
-        task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
-        test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
-        self.assertEqual(set(task_ids), test_task_ids,
-                         'access rights: portal user should see the followed tasks of a portal project')
 
         # Do: Donovan reads project -> ko (public ko portal)
         self.assertRaises(except_orm, self.project_project.read, cr, self.user_public_id, [pigs_id], ['state'])
@@ -197,6 +193,12 @@ class TestPortalProject(TestPortalProjectBase):
         task_ids = self.project_task.search(cr, self.user_public_id, [('project_id', '=', pigs_id)])
         self.assertFalse(task_ids, 'access rights: public user should not see tasks of an employees project')
 
+        # Do: project user is employee and can create a task
+        tmp_task_id = self.project_task.create(cr, self.user_projectuser_id, {
+            'name': 'Pigs task', 'project_id': pigs_id
+        }, {'mail_create_nolog': True})
+        self.project_task.unlink(cr, self.user_projectuser_id, [tmp_task_id])
+
         # ----------------------------------------
         # CASE4: followers project
         # ----------------------------------------
@@ -214,7 +216,8 @@ class TestPortalProject(TestPortalProjectBase):
         # Do: Bert reads project -> crash, no group
         self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, [pigs_id], ['state'])
 
-        # Do: Chell reads project -> ko (portal ko employee)
+        # Do: Chell reads project -> ko (portal ko followers)
+        self.project_project.message_unsubscribe_users(cr, self.user_portal_id, [pigs_id], [self.user_portal_id])
         self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, [pigs_id], ['state'])
         # Test: no project task visible
         task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
@@ -234,19 +237,25 @@ class TestPortalProject(TestPortalProjectBase):
 
         # Do: Alfred reads project -> ok (follower ok followers)
         self.project_project.read(cr, self.user_projectuser_id, [pigs_id], ['state'])
-        # Test: followed + assigned tasks visible
-        task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
-        test_task_ids = set([self.task_1_id, self.task_3_id, self.task_4_id])
-        self.assertEqual(set(task_ids), test_task_ids,
-                         'access rights: employee user should not see followed + assigned tasks of a follower project')
 
         # Do: Chell reads project -> ok (follower ok follower)
         self.project_project.read(cr, self.user_portal_id, [pigs_id], ['state'])
-        # Test: followed + assigned tasks visible
-        task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
-        test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
-        self.assertEqual(set(task_ids), test_task_ids,
-                         'access rights: employee user should not see followed + assigned tasks of a follower project')
 
         # Do: Donovan reads project -> ko (public ko follower even if follower)
         self.assertRaises(except_orm, self.project_project.read, cr, self.user_public_id, [pigs_id], ['state'])
+
+        # Do: project user is follower of the project and can create a task
+        self.project_task.create(cr, self.user_projectuser_id, {
+            'name': 'Pigs task', 'project_id': pigs_id
+        }, {'mail_create_nolog': True})
+
+        # not follower user should not be able to create a task
+        self.project_project.message_unsubscribe_users(cr, self.user_projectuser_id, [pigs_id], [self.user_projectuser_id])
+        self.assertRaises(except_orm,
+            self.project_task.create, cr, self.user_projectuser_id, {'name': 'Pigs task', 'project_id': pigs_id}, {'mail_create_nolog': True}
+        )
+
+        # Do: project user can create a task without project
+        self.project_task.create(cr, self.user_projectuser_id, {
+            'name': 'Pigs task', 'project_id': False
+        }, {'mail_create_nolog': True})
index 444a9d0..e1c2a8f 100644 (file)
     <record model="ir.rule" id="task_visibility_rule">
         <field name="name">Project/Task: employees: public or employee or (followers and following)</field>
         <field name="model_id" ref="model_project_task"/>
-        <field name="domain_force">['|',
-                                        ('project_id.privacy_visibility', 'in', ['public', 'employees']),
-                                        '&amp;',
-                                            '|',
-                                                ('project_id', '=', False),
-                                                ('project_id.privacy_visibility', '=', 'followers'),
-                                            ('message_follower_ids', 'in', [user.partner_id.id]),
-                                    ]</field>
+        <field name="domain_force">[
+        '|',
+            ('project_id.privacy_visibility', 'in', ['public', 'employees']),
+            '|',
+                '&amp;',
+                    ('project_id.privacy_visibility', '=', 'followers'),
+                    ('project_id.message_follower_ids', 'in', [user.partner_id.id]),
+                '|',
+                    ('message_follower_ids', 'in', [user.partner_id.id]),
+                    # to subscribe check access to the record, follower is not enough at creation
+                    ('user_id', '=', user.id)
+        ]</field>
         <field name="groups" eval="[(4,ref('base.group_user'))]"/>
     </record>