import datetime from unittest.mock import patch from odoo import Command, fields from odoo.addons.documents.tests.test_documents_common import TransactionCaseDocuments from odoo.addons.mail.tests.common import MockEmail from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tests import freeze_time, users from odoo.tools import html2plaintext, mute_logger from odoo.tests.common import RecordCapturer class TestDocumentsAccess(TransactionCaseDocuments, MockEmail): @mute_logger('odoo.addons.base.models.ir_model', 'odoo.addons.base.models.ir_rule') def test_access_type_internal(self): """Check that the 'internal' access_type_role works as expected.""" self.assertEqual(self.folder_a.access_internal, 'view') self._assert_no_members(self.folder_a) self.assertTrue(self.public_user._is_public()) self.assertTrue(self.portal_user._is_portal()) self._assert_raises_check_access_rule(self.folder_a.with_user(self.public_user)) self._assert_raises_check_access_rule(self.folder_a.with_user(self.portal_user)) self.folder_a.with_user(self.internal_user).check_access('read') self._assert_raises_check_access_rule(self.folder_a.with_user(self.internal_user), 'write') self.folder_a.access_internal = 'edit' self._assert_raises_check_access_rule(self.folder_a.with_user(self.public_user)) self._assert_raises_check_access_rule(self.folder_a.with_user(self.portal_user)) self.folder_a.with_user(self.internal_user).check_access('write') self.assertTrue(self.env['documents.document'].search([('id', '=', self.folder_a.id)])) self.assertFalse(self.env['documents.document'].with_user(self.portal_user).search([('id', '=', self.folder_a.id)])) @mute_logger('odoo.addons.base.models.ir_rule') def test_access_from_documents_access(self): """Check recursive access to documents via documents.access records.""" self.assertEqual(self.folder_a.access_internal, 'view') self.assertEqual(self.folder_a.access_via_link, 'none') self.assertEqual(self.folder_a_a.access_internal, 'view') self.assertEqual(self.folder_a_a.access_via_link, 'none') self._assert_no_members(self.folder_a) self.assertTrue(self.portal_user._is_portal()) folder_a_as_portal = self.folder_a.with_user(self.portal_user) folder_a_a_as_portal = self.folder_a_a.with_user(self.portal_user) folder_a_as_internal = self.folder_a.with_user(self.internal_user) self._assert_raises_check_access_rule(folder_a_as_portal) self._assert_raises_check_access_rule(folder_a_a_as_portal) portal_access = self.env['documents.access'].create({ 'document_id': self.folder_a.id, 'partner_id': self.portal_user.partner_id.id, 'role': 'view', }) folder_a_as_internal.check_access('read') self._assert_raises_check_access_rule(folder_a_as_internal, 'write') folder_a_as_portal.check_access('read') self._assert_raises_check_access_rule(folder_a_a_as_portal, 'read', 'No access given to A-A') self._assert_raises_check_access_rule(folder_a_as_portal, 'write') portal_access.role = 'edit' folder_a_as_portal.check_access('write') self.folder_a.access_internal = 'none' self._assert_raises_check_access_rule(folder_a_as_internal) folder_a_as_portal.check_access('write') @mute_logger('odoo.addons.base.models.ir_rule') def test_access_from_past_access(self): """Check recursive access to documents via documents.log records.""" self.folder_a.access_via_link = 'view' self.folder_a.access_internal = 'none' self.folder_a_a.access_via_link = 'view' self.folder_a_a.access_internal = 'none' self._assert_no_members(self.folder_a) self.assertTrue(self.portal_user._is_portal()) folder_a_as_portal = self.folder_a.with_user(self.portal_user) folder_a_a_as_portal = self.folder_a_a.with_user(self.portal_user) folder_a_as_internal = self.folder_a.with_user(self.internal_user) self.assertEqual(self.folder_a.access_ids.partner_id, self.doc_user.partner_id) # Owner's log self._assert_raises_check_access_rule(folder_a_as_portal) self.env['documents.access'].create({ 'document_id': self.folder_a.id, 'partner_id': self.portal_user.partner_id.id, 'last_access_date': fields.Datetime.now(), }) self._assert_raises_check_access_rule(folder_a_as_internal) folder_a_as_portal.check_access('read') folder_a_a_as_portal.check_access('read') self.assertEqual(folder_a_as_portal.user_permission, 'view') self._assert_raises_check_access_rule(folder_a_as_portal, 'write') self.folder_a.access_via_link = 'edit' folder_a_as_portal.check_access('write') self._assert_raises_check_access_rule(folder_a_a_as_portal, 'write') self.folder_a.access_via_link = 'none' self._assert_raises_check_access_rule(folder_a_as_portal) self._assert_raises_check_access_rule(folder_a_a_as_portal) # logged access it to parent, now mute. @mute_logger('odoo.addons.base.models.ir_rule') def test_access_from_search_panel_token(self): """Check that a shared folder (e.g. embedded view) is unlocked by its token.""" (self.folder_a + self.folder_a_a).write({'access_internal': 'none', 'access_via_link': 'none'}) self._assert_no_members(self.folder_a) folder_a_as_internal = self.folder_a.with_user(self.internal_user) self._assert_raises_check_access_rule(folder_a_as_internal, 'read') def select_range(folder_id=None, access_token=None): """Return the {folder name: parent} of the search panel of the folder.""" context = { **({'documents_unique_folder_id': folder_id} if folder_id else {}), **({'documents_shared_access_token': access_token} if access_token else {}), } values = self.env['documents.document'].with_user(self.internal_user).with_context( context).search_panel_select_range('user_folder_id')['values'] return {vals['display_name']: vals.get('user_folder_id') for vals in values} self.assertFalse(select_range(self.folder_a.id, self.folder_a.access_token), msg="Not shared via link") self.assertEqual(folder_a_as_internal.user_permission, "none") (self.folder_a + self.folder_a_a).access_via_link = 'view' self.assertFalse(select_range(self.folder_a.id), msg="No token") self.assertEqual(folder_a_as_internal.user_permission, "none") self.assertFalse(select_range(self.folder_a.id), msg="No token") self.assertEqual(folder_a_as_internal.user_permission, "none") all_range = select_range(None, self.folder_a.access_token) self.assertNotIn("folder a", all_range, msg="id not provided with token, save resources") self.assertNotIn("folder a - a", all_range) self.assertEqual(folder_a_as_internal.user_permission, "none") # With both, the folder and its content are unlocked expected = {'folder A': False, 'folder A - A': self.folder_a.id} with RecordCapturer(self.env["documents.access"]) as capture: self.assertEqual(select_range(self.folder_a.id, self.folder_a.access_token), expected) self.assertEqual(self.folder_a.with_user(self.internal_user).user_permission, "view") logged_access = capture.records self.assertEqual(logged_access.document_id, self.folder_a) self.assertFalse(logged_access.role) def test_access_rights_inherited_on_create(self): (self.folder_a + self.folder_b).write({ 'access_via_link': 'none', 'access_internal': 'none' }) self._assert_no_members(self.folder_a + self.folder_b) folder_a1 = self.env['documents.document'].create( {'name': 'Folder A1', 'folder_id': self.folder_a.id, 'type': 'folder'} ) # Owner of parent folder is given membership self.assertEqual(folder_a1.access_ids.partner_id, self.folder_a.owner_id.partner_id) self.assertEqual(folder_a1.access_internal, 'none') self.assertEqual(folder_a1.access_via_link, 'none') self.folder_a.access_internal = 'view' self.assertEqual(len(self.folder_a.access_ids), 1) # owner's log self.folder_a.action_update_access_rights(partners={self.portal_user.partner_id.id: ('view', False)}) self.assertEqual(len(self.folder_a.access_ids), 2) # owner's log + portal member self.folder_b.access_via_link = 'edit' self.folder_b.action_update_access_rights(partners={self.portal_user.partner_id.id: (False, False)}) folder_a2, folder_b1 = self.env['documents.document'].create([ {'name': 'Folder A2', 'folder_id': self.folder_a.id, 'type': 'folder'}, {'name': 'Folder B1', 'folder_id': self.folder_b.id, 'type': 'folder'}, ]) self.assertEqual(len(folder_a2.access_ids), 2) # inherited portal member + folder_a's owner self.assertEqual(folder_a2.access_ids.partner_id, (self.portal_user + self.doc_user).partner_id) self.assertEqual(folder_a2.access_internal, 'view') self.assertEqual(folder_a2.access_via_link, 'none') self.assertEqual(folder_b1.access_ids.partner_id, self.doc_user.partner_id) # parent's owner (!= from child) self.assertEqual(folder_b1.access_internal, 'none') self.assertEqual(folder_b1.access_via_link, 'edit') folder_a3, folder_a4 = self.env['documents.document'].create([ {'name': 'Folder A3', 'folder_id': self.folder_a.id, 'type': 'folder', 'access_ids': False}, {'name': 'Folder A4', 'folder_id': self.folder_a.id, 'type': 'folder', 'access_ids': [ Command.create({'partner_id': self.internal_user.partner_id.id, 'role': 'view'}) ]}, ]) self.assertFalse(folder_a3.access_ids) self.assertEqual(len(folder_a4.access_ids), 3) self.assertEqual( folder_a4.access_ids.mapped(lambda a: (a.partner_id, a.role)), [ (self.internal_user.partner_id, 'view'), (self.portal_user.partner_id, 'view'), (self.doc_user.partner_id, 'edit'), ] ) self.folder_a.access_internal = 'edit' self.folder_a.access_via_link = 'view' # Create another log self.env['documents.access'].create({ 'document_id': self.folder_a.id, 'partner_id': self.internal_user.partner_id.id, 'last_access_date': fields.Datetime.now(), }) self.assertEqual(len(self.folder_a.access_ids), 3) folder_a5 = self.env['documents.document'] \ .with_context(default_access_internal='view', default_folder_id=self.folder_a.id) \ .create({'name': 'Folder A5', 'type': 'folder'}) self.assertEqual(len(folder_a5.access_ids), 2) # Inherited from member + owner, not logged access self.assertEqual(folder_a5.access_ids.partner_id, (self.portal_user + self.doc_user).partner_id) self.assertEqual(folder_a5.access_internal, 'edit') self.assertEqual(folder_a5.access_via_link, 'view') folder_a6 = self.env['documents.document'] \ .with_context(default_access_internal='view', default_folder_id=self.folder_a.id) \ .create({'name': 'Folder A6', 'type': 'folder', 'access_ids': False, 'access_internal': 'none'}) self.assertFalse(folder_a6.access_ids) self.assertEqual(folder_a6.access_internal, 'none') self.assertEqual(folder_a6.access_via_link, 'view') @mute_logger('odoo.addons.base.models.ir_rule') def test_access_owner(self): self.folder_a.write({ 'access_via_link': 'none', 'access_internal': 'none' }) self._assert_no_members(self.folder_a) self._assert_raises_check_access_rule(self.folder_a.with_user(self.internal_user)) self.assertEqual(self.folder_a.owner_id, self.doc_user) self.folder_a.with_user(self.doc_user).check_access('read') self.folder_a.with_user(self.doc_user).check_access('write') @mute_logger('odoo.addons.base.models.ir_rule') def test_documents_access_cu(self): secret = self.env['documents.document'].create({ 'access_internal': 'none', 'access_via_link': 'none', 'name': 'secret', }) public = self.env['documents.document'].create({ 'access_internal': 'edit', 'access_via_link': 'none', 'name': 'secret', }) secret_id = secret.id self.env.invalidate_all() with self.assertRaises(AccessError): secret.with_user(self.doc_user).name with self.assertRaises(AccessError): self.env['documents.access'].with_user(self.doc_user).create({ 'role': 'edit', 'document_id': secret_id, 'partner_id': self.doc_user.partner_id.id, }) with self.assertRaises(AccessError): self.env['documents.access'].with_user(self.doc_user).with_context( default_document_id=secret_id).create({ 'role': 'edit', 'partner_id': self.doc_user.partner_id.id, }) access = self.env['documents.access'].with_user(self.doc_user).create({ 'role': 'edit', 'document_id': public.id, 'partner_id': self.doc_user.partner_id.id, }) with self.assertRaises(AccessError): access.document_id = secret_id with self.assertRaises(AccessError): access.copy(default={'document_id': secret_id}) access_admin = self.env['documents.access'].create({ 'role': 'edit', 'document_id': secret_id, 'partner_id': self.env.user.partner_id.id, }) with self.assertRaises(AccessError): access_admin.with_user(self.doc_user).unlink() # check that the user can not upgrade a view access access = self.env['documents.access'].create({ 'role': 'view', 'document_id': secret.id, 'partner_id': self.doc_user.partner_id.id, }) with self.assertRaises(AccessError): access.with_user(self.doc_user).role = 'edit' with self.assertRaises(AccessError): access.with_user(self.doc_user).expiration_date = datetime.datetime.now() @mute_logger('odoo.addons.base.models.ir_rule') def test_access_users_drive_is_private(self): # Make folder_a a folder in internal users' drive self.folder_a.write({ 'access_internal': 'none', 'access_via_link': 'none', 'folder_id': False, 'owner_id': self.internal_user, }) self.folder_a.access_ids = False self._assert_no_members(self.folder_a) not_authorized = self.doc_user + self.document_manager + self.portal_user for not_authorized_user in not_authorized: with self.subTest(user=not_authorized_user.name): folder_a_with_user = self.folder_a.with_user(not_authorized_user) self.assertEqual(self.folder_a.with_user(self.doc_user).user_permission, 'none') self.assertFalse(folder_a_with_user.search([('id', '=', self.folder_a.id)])) self._assert_raises_check_access_rule(folder_a_with_user) def test_authorized_users(authorized_users): for authorized_user in authorized_users: folder_a = self.folder_a.with_user(authorized_user) with self.subTest(user=authorized_user.name, method='compute'): self.assertEqual(folder_a.user_permission, 'edit') with self.subTest(user=authorized_user.name, method='search'): self.assertEqual( folder_a.search([('id', '=', folder_a.id), ('user_permission', '=', 'edit')]), folder_a ) self.assertEqual(folder_a.search([('id', '=', self.folder_a.id)]), self.folder_a) self.document_manager.group_ids |= self.env.ref('documents.group_documents_system') test_authorized_users(self.internal_user + self.document_manager) self.folder_a.action_update_access_rights(partners={self.portal_user.partner_id: ('edit', False)}) test_authorized_users(self.portal_user) self.folder_a.action_update_access_rights(access_internal='edit') test_authorized_users(self.doc_user + self.document_manager) self.folder_a.action_update_access_rights(access_internal='view') test_authorized_users(self.document_manager) @mute_logger('odoo.addons.base.models.ir_rule') def test_action_update_access_rights_partners(self): """Check that we can update partners access to a document.""" self._assert_no_members(self.folder_a) portal_user_2 = self.portal_user.copy() folder_a_as_portal = self.folder_a.with_user(self.portal_user) folder_a_as_portal_2 = self.folder_a.with_user(portal_user_2) self._assert_raises_check_access_rule(folder_a_as_portal) self._assert_raises_check_access_rule(folder_a_as_portal_2) # Make portal and portal 2 viewers, permanently or for one day, resp. IN_ONE_DAY = fields.Datetime.now() + datetime.timedelta(days=1) partners = { self.portal_user.partner_id.id: ('view', False), portal_user_2.partner_id.id: ('view', IN_ONE_DAY) } self.env.invalidate_all() with self.assertQueryCount(13): self.folder_a.action_update_access_rights(partners=partners) folder_a_as_portal.check_access('read') folder_a_as_portal_2.check_access('read') folder_a_as_internal = self.folder_a.with_user(self.internal_user) # Check that expiration was propagated too portal_2_a_a_access = self.folder_a_a.access_ids.filtered( lambda a: a.partner_id == portal_user_2.partner_id) self.assertEqual(len(portal_2_a_a_access), 1) self.assertEqual(portal_2_a_a_access.expiration_date, IN_ONE_DAY) # Update expiration alone via parent IN_12_H = IN_ONE_DAY - datetime.timedelta(hours=12) self.env.invalidate_all() with self.assertQueryCount(8): self.folder_a.action_update_access_rights(partners={portal_user_2.partner_id: ('view', IN_12_H)}) self.assertEqual(portal_2_a_a_access.expiration_date, IN_12_H) # Update role+expiration via parent self.env.invalidate_all() with self.assertQueryCount(8): self.folder_a.action_update_access_rights(partners={portal_user_2.partner_id: ('edit', IN_ONE_DAY)}) self.assertEqual(portal_2_a_a_access.expiration_date, IN_ONE_DAY) self.assertEqual(portal_2_a_a_access.role, 'edit') # Update role alone via parent self.env.invalidate_all() with self.assertQueryCount(8): self.folder_a.action_update_access_rights(partners={portal_user_2.partner_id: ('view', None)}) self.assertEqual(portal_2_a_a_access.expiration_date, IN_ONE_DAY) self.assertEqual(portal_2_a_a_access.role, 'view') # Make portal viewer of grandchild folder only partners = {self.portal_user.partner_id.id: (False, None)} self.env.invalidate_all() with self.assertQueryCount(10): self.folder_a.action_update_access_rights(partners=partners) self.assertFalse(self.folder_a.access_ids.filtered(lambda a: a.partner_id == self.portal_user.partner_id)) self._assert_raises_check_access_rule(folder_a_as_portal, 'read') folder_a_a_p = self.env['documents.document'].create({ 'type': 'folder', 'name': "A cool name", 'folder_id': self.folder_a_a.id, }) partners = {self.portal_user.partner_id.id: ('view', False)} self.env.invalidate_all() with self.assertQueryCount(7): folder_a_a_p.action_update_access_rights(partners=partners) # Make portal and internal editors of parent folder, this should propagate down partners = { partner_id: ('edit', False) for partner_id in (self.portal_user | self.internal_user).partner_id.ids } self.env.invalidate_all() with self.assertQueryCount(8): self.folder_a.action_update_access_rights(partners=partners) folder_a_as_portal.check_access('write') folder_a_a_as_portal = self.folder_a_a.with_user(self.portal_user) folder_a_a_as_portal.check_access('write') folder_a_as_internal.check_access('write') self._assert_raises_check_access_rule(folder_a_as_portal_2, 'write') # Remove portal 2 access portal_2_partner_id = portal_user_2.partner_id.id self.env.invalidate_all() with self.assertQueryCount(7): self.folder_a.action_update_access_rights(partners={portal_2_partner_id: (False, False)}) self._assert_raises_check_access_rule(folder_a_as_portal_2) # Add portal 2 access to 1st level child and remove from 2nd self.env.invalidate_all() with self.assertQueryCount(7): self.folder_a_a.action_update_access_rights(partners={portal_2_partner_id: ('view', False)}) folder_a_a_p.action_update_access_rights(partners={portal_user_2.partner_id.id: (False, None)}) self.assertFalse(folder_a_a_p.access_ids.filtered(lambda a: a.partner_id == portal_user_2.partner_id)) # check that the accesses are propagated on the documents even if they are in the trash folder_a_a_p.action_archive() self.folder_a_a.action_update_access_rights(partners={portal_user_2.partner_id.id: ('edit', None)}) self.assertEqual(folder_a_a_p.access_ids.filtered(lambda a: a.partner_id == portal_user_2.partner_id).role, 'edit') def test_action_update_access_rights_internal_propagation(self): self.assertEqual(set((self.folder_b + self.document_gif).mapped('access_internal')), {'view'}) self.folder_b.action_update_access_rights(access_internal='none') self.assertEqual(set((self.folder_b + self.document_gif).mapped('access_internal')), {'none'}) # check that the accesses are propagated on the documents even if they are in the trash self.document_gif.action_archive() self.folder_b.action_update_access_rights(access_internal='edit') self.assertEqual(set((self.folder_b + self.document_gif).mapped('access_internal')), {'edit'}) self.document_gif.action_update_access_rights(access_internal='view') self.assertEqual((self.folder_b + self.document_gif).mapped('access_internal'), ['edit', 'view']) def test_action_update_access_rights_no_propagation(self): doc_partner = self.doc_user.partner_id self.folder_b.action_update_access_rights( access_internal='view', access_via_link='view', is_access_via_link_hidden=True, partners={self.doc_user.partner_id: ('view', False)}) self.assertEqual(self.folder_b.access_ids.filtered(lambda a: a.partner_id == doc_partner).mapped( lambda a: (a.role, a.expiration_date)), [('view', False)]) self.assertEqual(self.document_gif.access_ids.filtered(lambda a: a.partner_id == doc_partner).mapped( lambda a: (a.role, a.expiration_date)), [('view', False)]) self.assertEqual(self.folder_b.mapped( lambda d: (d.access_internal, d.access_via_link, d.is_access_via_link_hidden)), [('view', 'view', True)]) self.assertEqual(self.document_gif.mapped(lambda d: (d.access_internal, d.access_via_link)), [('view', 'view')]) self.assertFalse(self.document_gif.is_access_via_link_hidden, "`is_access_via_link_hidden` should not propagate") IN_ONE_DAY = fields.Datetime.now() + datetime.timedelta(days=1) self.document_txt.is_access_via_link_hidden = True self.folder_b.action_update_access_rights( access_internal='edit', access_via_link='edit', is_access_via_link_hidden=False, partners={doc_partner: ('edit', IN_ONE_DAY)}, no_propagation=True) self.assertEqual(self.folder_b.mapped( lambda d: (d.access_internal, d.access_via_link, d.is_access_via_link_hidden)), [('edit', 'edit', False)]) self.assertEqual(self.folder_b.access_ids.filtered(lambda a: a.partner_id == doc_partner).mapped( lambda a: (a.role, a.expiration_date)), [('edit', IN_ONE_DAY)]) self.assertEqual(self.document_gif.mapped( lambda d: (d.access_internal, d.access_via_link, d.is_access_via_link_hidden)), [('view', 'view', False)]) self.assertEqual(self.document_txt.mapped( lambda d: (d.access_internal, d.access_via_link, d.is_access_via_link_hidden)), [('view', 'view', True)]) self.assertEqual(self.document_gif.access_ids.filtered(lambda a: a.partner_id == doc_partner).mapped( lambda a: (a.role, a.expiration_date)), [('view', False)]) def test_action_update_access_rights_link_propagation(self): self.assertEqual(set((self.folder_b + self.document_gif).mapped('access_via_link')), {'none'}) self.folder_b.action_update_access_rights(access_via_link='view') self.assertEqual(set((self.folder_b + self.document_gif).mapped('access_via_link')), {'view'}) @mute_logger('odoo.addons.base.models.ir_rule') def test_action_update_access_rights_sudo(self): """Test that the action will update rights on restricted access folder when using sudo.""" self.folder_a.write({ 'access_internal': 'none', 'access_via_link': 'none', 'folder_id': False, 'owner_id': self.document_manager, }) self.folder_a.access_ids = False self._assert_no_members(self.folder_a) folder_a_as_internal = self.folder_a.with_user(self.internal_user) # Members with self.assertRaises(AccessError): folder_a_as_internal.action_update_access_rights(partners={self.internal_user.partner_id: ('edit', None)}) folder_a_as_internal.sudo().action_update_access_rights(partners={self.internal_user.partner_id: ('edit', None)}) @users('documents@example.com') def test_moving_documents(self): """Check that documents can be moved to a new location and have their rights updated except discoverability.""" self.folder_b.write({ 'access_internal': 'none', 'access_via_link': 'none', 'is_access_via_link_hidden': True, }) self.folder_a.write({'access_via_link': 'view', 'is_access_via_link_hidden': False}) self.folder_a_a.folder_id = self.folder_b.id self.assertEqual(self.folder_a_a.folder_id, self.folder_b) self.folder_b.folder_id = self.folder_a.id self.assertEqual(self.folder_b.folder_id, self.folder_a) self.assertEqual(self.folder_b.access_internal, 'view', 'Internal access should have been updated.') self.assertEqual(self.folder_b.access_via_link, 'view', 'link access should have been updated.') self.assertEqual(self.folder_b.is_access_via_link_hidden, True, 'Discoverability should not be updated') self.document_gif.folder_id = False shortcut = self.folder_b.with_user(self.doc_user).action_create_shortcut(location_user_folder_id='MY') self.document_gif.folder_id = shortcut.id self.assertEqual(self.document_gif.folder_id, self.folder_b) doc_shortcut = self.document_gif.action_create_shortcut(str(shortcut.id)) self.assertEqual(doc_shortcut.folder_id, self.folder_b) # making a shortcut of a shortcut use the target instead shortcut = shortcut.action_create_shortcut(location_user_folder_id='MY') self.assertEqual(shortcut.shortcut_document_id, self.folder_b) # Can't move out if not editor on folder self.folder_a.action_update_access_rights(partners={self.internal_user.partner_id.id: ('edit', False)}) self.folder_b.action_update_access_rights(partners={self.internal_user.partner_id.id: ('view', False)}) self.document_gif.action_update_access_rights(partners={self.internal_user.partner_id.id: ('edit', False)}) with self.assertRaises(AccessError): self.document_gif.with_user(self.internal_user).folder_id = self.folder_a # Unless user is the owner self.document_gif.owner_id = self.internal_user self.document_gif.with_user(self.internal_user).folder_id = self.folder_a @mute_logger('odoo.addons.base.models.ir_model', 'odoo.addons.base.models.ir_rule') def test_create_document_access(self): with self.assertRaises(AccessError): self.folder_a.with_user(self.internal_user).name = 'test' with self.assertRaises(AccessError): self.env['documents.document'].with_user(self.internal_user).create({ 'name': 'folder', 'folder_id': self.folder_a.id, 'owner_id': self.internal_user.id, 'type': 'folder', }) # internal user can create a file on the root self.env['documents.document'].with_user(self.internal_user).create({ 'name': 'document', 'folder_id': False, 'owner_id': self.internal_user.id, 'type': 'binary', }) with self.assertRaises(UserError): # portal can not be the owner of a document on the root self.env['documents.document'].with_user(self.portal_user).create({ 'name': 'document', 'folder_id': False, 'owner_id': self.portal_user.id, 'type': 'binary', }) with self.assertRaises(UserError): # and portal can not create a document on the root for an other user self.env['documents.document'].with_user(self.portal_user).create({ 'name': 'document', 'folder_id': False, 'owner_id': self.internal_user.id, 'type': 'binary', }) # but in SUDO, portal should be able to create a document on the root self.env['documents.document'].with_user(self.portal_user).sudo().create({ 'name': 'document', 'folder_id': False, 'owner_id': self.internal_user.id, 'type': 'binary', }) with self.assertRaises(UserError): # even in SUDO, portal can not be set as the owner of a root document self.env['documents.document'].with_user(self.portal_user).sudo().create({ 'name': 'document', 'folder_id': False, 'owner_id': self.portal_user.id, 'type': 'binary', }) @mute_logger('odoo.addons.base.models.ir_rule') def test_restrict_write_on_pinned_folders(self): """Check that editing pinned folders is restricted to managers Creating inside depends on regular access rights """ self.assertFalse(self.folder_a.folder_id) self.folder_a.owner_id = False self.folder_a.action_update_access_rights(partners={self.internal_user.partner_id.id: ('edit', False)}) access = self.env['documents.access'].search([ ('document_id', '=', self.folder_a.id), ('partner_id', '=', self.internal_user.partner_id.id), ]) self.assertEqual(len(access), 1) self.assertEqual(access.role, 'edit') with self.assertRaises(AccessError): # Un-pin attempt self.folder_a.with_user(self.internal_user).folder_id = self.folder_b # That's because self._assert_raises_check_access_rule(self.folder_a.with_user(self.internal_user), 'write') # but user_permission = 'edit' and can create inside self.assertEqual(self.folder_a.with_user(self.internal_user).user_permission, 'edit') self.env['documents.document'].with_user(self.internal_user).create({ 'type': 'folder', 'name': 'a folder', 'folder_id': self.folder_a.id }) # or create a shortcut inside shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_user_folder_id=str(self.folder_a.id) ) self.assertEqual(shortcut.folder_id, self.folder_a) # Managers can unpin by moving to another root folder self.folder_b.owner_id = False self.folder_a.with_user(self.document_manager).folder_id = self.folder_b self.assertFalse(self.folder_a._is_company_root_folder()) # Or moving to their own drive self.folder_a.with_user(self.document_manager).folder_id = False self.folder_a.with_user(self.document_manager).owner_id = self.document_manager @mute_logger('odoo.addons.base.models.ir_rule') def test_pin_folder_create(self): """Check that a normal user can not create a pinned folder.""" folder = self.env['documents.document'].create({ 'folder_id': False, 'name': 'folder', 'owner_id': False, 'type': 'folder', }) self.assertTrue(folder._is_company_root_folder()) with self.assertRaises(AccessError): self.env['documents.document'].with_user(self.internal_user).create({ 'folder_id': False, 'name': 'folder', 'owner_id': False, 'type': 'folder', }) @mute_logger('odoo.addons.base.models.ir_rule') def test_pin_folder_folder_id(self): """Check that non-admins cannot (un-)pin company root folders.""" self.assertFalse(self.folder_a.folder_id) self.folder_a.owner_id = self.document_manager self.folder_a.access_ids = False self.folder_a.action_update_access_rights( access_internal='none', partners={self.internal_user.partner_id.id: ('edit', False)} ) children_access_before = [access_ids.filtered(lambda a: a.role) for access_ids in self.folder_a.children_ids.mapped(lambda f: f.access_ids)] for child_access_before in children_access_before: self.assertFalse(self.document_manager.partner_id & child_access_before.partner_id) # managers pins from their drive self.folder_a.with_user(self.document_manager).user_folder_id = "COMPANY" self.assertEqual( (self.document_manager | self.internal_user).partner_id, self.folder_a.access_ids.filtered(lambda a: a.role).partner_id) # Manager access was not added on children self.assertEqual( [access_ids.filtered(lambda a: a.role) for access_ids in self.folder_a.children_ids.mapped(lambda f: f.access_ids)], children_access_before) # internal user cannot write on pinned folders self._assert_raises_check_access_rule(self.folder_a.with_user(self.internal_user), 'write') self.folder_b.children_ids.action_update_access_rights(partners={self.doc_user.partner_id: (False, False)}) self.folder_b.folder_id = False self.assertEqual(self.folder_b.owner_id, self.doc_user) self.folder_b.action_update_access_rights( access_internal='edit', partners={self.internal_user.partner_id.id: ('edit', False)} ) children_access_before = [access_ids.filtered(lambda a: a.role) for access_ids in self.folder_b.children_ids.mapped(lambda f: f.access_ids)] for child_access_before in children_access_before: self.assertFalse(self.doc_user.partner_id & child_access_before.partner_id) # managers can pin accessible folders (here in other user's drive) self.folder_b.with_user(self.document_manager).user_folder_id = "COMPANY" # previous owner is added as member self.assertEqual( (self.doc_user | self.internal_user).partner_id, self.folder_b.access_ids.filtered(lambda a: a.role).partner_id) # Previous owner access was not added on children self.assertEqual( [access_ids.filtered(lambda a: a.role) for access_ids in self.folder_b.children_ids.mapped(lambda f: f.access_ids)], children_access_before) # set_as_company_root changed owner_id self.assertFalse((self.folder_a | self.folder_b).owner_id) # Normal user cannot pin self.folder_a.with_user(self.document_manager).owner_id = self.internal_user self.folder_a.with_user(self.internal_user).check_access('write') with self.assertRaises(AccessError): self.folder_a.with_user(self.internal_user).user_folder_id = "COMPANY" # with SUDO, a normal user can pin a folder self.folder_a.with_user(self.internal_user).sudo().user_folder_id = "COMPANY" # with SUDO, a normal user can move a pinned a folder self.folder_a.with_user(self.internal_user).sudo().owner_id = self.internal_user # Internal user cannot copy a company root folder with self.assertRaises(AccessError): self.company_root_folder.with_user(self.internal_user).copy(default={'user_folder_id': "COMPANY"}) @mute_logger('odoo.addons.base.models.ir_rule') def test_unlink_with_children(self): """Check that deletion handles children items and checks access rights This is necessary to avoid handling changes of access rights when moving records to parent folders, showing them in the trash etc. This is also important to make sure that an 2nd level folder without owner cannot be pinned by deleting its parent """ self.folder_a.action_update_access_rights(access_internal='edit') self.folder_a_a.action_update_access_rights(access_internal='none') with self.assertRaises(AccessError): self.folder_a.with_user(self.internal_user).unlink() self.folder_a_a.action_update_access_rights(access_internal='edit') self.folder_a.with_user(self.internal_user).unlink() self.assertFalse(self.folder_a_a.exists()) @mute_logger('odoo.addons.base.models.ir_rule') def test_archiving_as_portal(self): """Check that (un)archiving is not allowed to portal users.""" self.folder_a.action_update_access_rights(partners={self.portal_user.partner_id: ("edit", False)}) with self.assertRaises(UserError): self.folder_a.with_user(self.portal_user).action_archive() self.folder_a.action_archive() self.assertFalse(self.folder_a.active) with self.assertRaises(UserError): self.folder_a.with_user(self.portal_user).action_unarchive() self.folder_a.with_user(self.portal_user).sudo().action_unarchive() @mute_logger('odoo.addons.base.models.ir_rule') def test_archiving_with_children(self): """Check that archiving handles children items and checks access rights. See also ``test_unlink_with_children`` """ self.folder_a.action_update_access_rights(access_internal='edit') self.folder_a_a.action_update_access_rights(access_internal='none') with self.assertRaises(AccessError): self.folder_a.with_user(self.internal_user).action_archive() self.folder_a_a.action_update_access_rights(access_internal='edit') self.folder_a.with_user(self.internal_user).action_archive() self.assertFalse(self.folder_a_a.active) @mute_logger('odoo.addons.base.models.ir_rule') def test_access_expiration(self): """Check that expired access_ids no longer provide access.""" self._assert_no_members(self.folder_a) self.folder_a.action_update_access_rights(partners={self.portal_user.partner_id.id: ('view', False)}) folder_a_as_portal = self.folder_a.with_user(self.portal_user) folder_a_as_portal.check_access('read') first_child_as_portal = folder_a_as_portal.children_ids self.folder_a.action_update_access_rights(partners={ self.portal_user.partner_id.id: ('view', fields.Datetime.now() - datetime.timedelta(days=1))}) self.assertEqual(len(first_child_as_portal), 1) self._assert_raises_check_access_rule(folder_a_as_portal, 'read') # As we did update children self._assert_raises_check_access_rule(first_child_as_portal, 'read') @mute_logger('odoo.addons.base.models.ir_rule') def test_access_via_link_from_parent_folder(self): """Check that a document accessible via link is accessible to users having access to the parent folder. Note that access via link to the parent is only checked if there is an access record (albeit without role). This is used to: * share a folder with its similarly-configured contents to a public user with a single token (incl. download a zip). * make files discoverable when navigating the parent folder in the client without creating `access` records for every file when 'reading' the folder. """ self._assert_no_members(self.folder_b) self.folder_b.action_update_access_rights(access_internal='none', access_via_link='none') # with propagation self.assertEqual(self.folder_b.access_internal, 'none') self.assertEqual(self.document_gif.access_internal, 'none') self.assertEqual(self.document_txt.access_internal, 'none') self.assertEqual(self.document_gif.access_via_link, 'none') self.assertEqual(self.document_txt.access_via_link, 'view') with mute_logger('odoo.addons.documents.models.documents_document'): # Creating document(s) as superuser document_txt_private = self.document_txt.copy() self.assertIn(document_txt_private.access_ids.role, {False, 'edit'}) document_txt_private.is_access_via_link_hidden = True self.assertEqual(document_txt_private.access_via_link, 'view') gif_as_internal = self.document_gif.with_user(self.internal_user) txt_as_internal = self.document_txt.with_user(self.internal_user) txt_private_as_internal = document_txt_private.with_user(self.internal_user) self._assert_raises_check_access_rule(gif_as_internal | txt_as_internal, 'read') access_values = {'document_id': self.folder_b.id, 'partner_id': self.internal_user.partner_id.id} folder_accesses_values = [ ('internal user access', {'access_internal': 'view'}), ('link accessed', { 'access_via_link': 'view', 'access_ids': [Command.create(access_values | {'last_access_date': fields.Datetime.now()})] }), ('member', {'access_ids': [Command.create(access_values | {'role': 'view'})]}), ] for case_name, folder_access_vals in folder_accesses_values: with self.subTest(case_name=case_name): self.folder_b.write(folder_access_vals) self._assert_raises_check_access_rule(gif_as_internal, 'read') self._assert_raises_check_access_rule(txt_private_as_internal, 'read') self.assertEqual((gif_as_internal | txt_private_as_internal).mapped('user_permission'), ['none', 'none']) self.assertEqual(txt_as_internal.search([('folder_id', '=', self.folder_b.id)]), self.document_txt) txt_as_internal.check_access('read') if case_name == 'internal user access': # Difference between internal and portal self._assert_raises_check_access_rule(self.document_txt.with_user(self.portal_user), 'read') # cleanup self.folder_b.write({'access_internal': 'none', 'access_via_link': 'none'}) self.folder_b.access_ids.unlink() def test_access_documents_and_attachment(self): self.document_gif.action_update_access_rights(access_internal='none', access_via_link='none') gif_as_internal = self.document_gif.with_user(self.internal_user) self.assertFalse(self.document_gif.res_id) self.assertFalse(self.document_gif.res_model) self.assertEqual(self.document_gif.attachment_id.res_id, self.document_gif.id) self.assertEqual(self.document_gif.attachment_id.res_model, 'documents.document') self._assert_raises_check_access_rule(gif_as_internal, 'read') # Check that the user can not read the attachment self.assertFalse(self.document_gif.res_model) with self.assertRaises(AccessError): self.assertEqual(self.document_gif.attachment_id.with_user(self.internal_user).name, 'file.gif') # If we give access to the document, the user should be able to read both the document and the attachment self.document_gif.access_internal = 'view' self.assertEqual(gif_as_internal.with_user(self.internal_user).name, 'file.gif') self.assertFalse(gif_as_internal.res_model) self.env.invalidate_all() self.assertEqual(self.document_gif.attachment_id.with_user(self.internal_user).name, 'file.gif') @mute_logger('odoo.addons.base.models.ir_rule') def test_access_rights_shortcuts_and_discoverability(self): """Check access rights related to shortcuts: * A shortcut pointing to a non-accessible document is hidden. * A shortcut pointing to a non-accessible document but discoverable with link should be visible. See also ``test_access_via_link_from_parent_folder``. """ self._assert_no_members(self.folder_b) self.folder_b.folder_id = self.folder_a.id self.assertEqual((self.folder_b + self.document_txt).access_ids.mapped('role'), [False, 'edit']) self.folder_a.owner_id = False self.assertEqual(self.folder_a.access_internal, 'view') self.assertEqual(self.document_txt.folder_id, self.folder_b) self.folder_a.action_update_access_rights(partners={self.portal_user.partner_id: ('view', False)}) (self.folder_b + self.document_txt).action_update_access_rights( access_internal='none', access_via_link='none', partners={self.portal_user.partner_id: (False, False)}) self.assertEqual(self.document_txt.with_user(self.portal_user).user_permission, 'none') self._assert_raises_check_access_rule(self.document_txt.with_user(self.portal_user), 'read') # Create a shortcut to document_txt in folder_a shortcut = self.document_txt.action_create_shortcut(location_user_folder_id=str(self.folder_a.id)) self.assertEqual( shortcut.access_ids.mapped(lambda a: (a.role, a.partner_id)), [('edit', self.doc_user.partner_id)]) self._assert_raises_check_access_rule(shortcut.with_user(self.portal_user), 'read', "Shortcut shouldn't be visible as source is inaccessible") self.assertEqual(shortcut.with_user(self.portal_user).user_permission, 'none') docs = self.document_txt + shortcut # Neither internal nor manager can't see any one of the two self.assertSetEqual(set(docs.with_user(self.internal_user).mapped('user_permission')), {'none'}) self._assert_raises_check_access_rule(docs.with_user(self.internal_user), 'read') self.assertSetEqual(set(docs.with_user(self.document_manager).mapped('user_permission')), {'none'}) self._assert_raises_check_access_rule(docs.with_user(self.document_manager), 'read') # Let internal see both self.document_txt.action_update_access_rights(access_internal='view') self.assertSetEqual(set(docs.with_user(self.internal_user).mapped('user_permission')), {'view'}) docs.with_user(self.internal_user).check_access('read') # Manager now has edit right self.assertSetEqual(set(docs.with_user(self.document_manager).mapped('user_permission')), {'edit'}) docs.with_user(self.document_manager).check_access('write') # Still not portal self.assertSetEqual(set(docs.with_user(self.portal_user).mapped('user_permission')), {'none'}) self._assert_raises_check_access_rule(docs.with_user(self.portal_user), 'read') self.document_txt.action_update_access_rights(access_via_link='view', is_access_via_link_hidden=True) self.assertEqual(shortcut.access_via_link, 'view') self.assertEqual(shortcut.is_access_via_link_hidden, True) self._assert_raises_check_access_rule(shortcut.with_user(self.portal_user), 'read', "Shortcut shouldn't be visible as source is still inaccessible") self.assertEqual(shortcut.with_user(self.portal_user).user_permission, 'none') self.document_txt.action_update_access_rights(is_access_via_link_hidden=False) self.assertEqual(self.document_txt.is_access_via_link_hidden, False) self.assertEqual(shortcut.is_access_via_link_hidden, False) self.assertEqual(shortcut.with_user(self.portal_user).user_permission, 'view') shortcut.with_user(self.portal_user).check_access('read') # Shortcut target is still inaccessible, it will be made available when logging access self._assert_raises_check_access_rule(self.document_txt.with_user(self.portal_user), 'read') # Updating the access on the target update the access on the shortcut itself partner = self.env['res.partner'].create({'name': 'Test'}) shortcut.shortcut_document_id.action_update_access_rights( access_internal='edit', partners={partner: ('edit', False)}) self.assertEqual(shortcut.shortcut_document_id.access_internal, 'edit') self.assertEqual(shortcut.access_internal, 'edit') access = self.env['documents.access'].search([('partner_id', '=', partner.id)]) self.assertEqual(len(access), 2) self.assertEqual(access.document_id, shortcut | shortcut.shortcut_document_id) shortcut.shortcut_document_id.action_update_access_rights( access_internal='edit', partners={partner: (False, False)}) self.assertFalse(access.exists()) # Check that own shortcut is deleted when access to the target is removed # (avoids client fetches on inaccessible previews & others). self.folder_a.action_update_access_rights(access_internal='edit') self.document_txt.action_update_access_rights(access_internal='view', access_via_link='none') # Access via access_internal shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_user_folder_id=str(self.folder_a.id) ) self.assertEqual(shortcut.owner_id, self.internal_user) self.document_txt.action_update_access_rights(access_internal='none') self.assertEqual(self.document_txt.with_user(self.internal_user).user_permission, 'none') self.assertFalse( self.env['documents.document'].with_user(self.internal_user).search([('id', '=', shortcut.id)])) self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'none') # Access via membership self.document_txt.action_update_access_rights( partners={self.internal_user.partner_id: ('view', False)}) shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_user_folder_id=str(self.folder_a.id) ) self.assertEqual(shortcut.owner_id, self.internal_user) self.document_txt.action_update_access_rights(partners={self.internal_user.partner_id: (False, False)}) self.assertFalse( self.env['documents.document'].with_user(self.internal_user).search([('id', '=', shortcut.id)])) self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'none') # Access via ownership self.document_txt.owner_id = self.internal_user shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_user_folder_id=str(self.folder_a.id) ) self.assertEqual(shortcut.owner_id, self.internal_user) self.document_txt.owner_id = self.document_manager self.assertFalse( self.env['documents.document'].with_user(self.internal_user).search([('id', '=', shortcut.id)])) self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'none') def test_access_rights_shortcuts_target(self): Doc_as_internal = self.env['documents.document'].with_user(self.internal_user) # Check in SUDO to remove the added `user_permission` domain from the access rules Doc_as_internal_sudo = Doc_as_internal.sudo() shortcut = self.document_txt.action_create_shortcut(location_user_folder_id=str(self.folder_a.id)) # Check edit access on the shortcut when we can only read the target self.document_txt.action_update_access_rights(access_internal='view', access_via_link='none') shortcut.sudo().owner_id = self.internal_user self.assertEqual(self.document_txt.with_user(self.internal_user).user_permission, 'view') self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'edit') self.assertTrue(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '=', 'edit')])) doc_class = Doc_as_internal_sudo.pool['documents.document'] original_search = doc_class._search_user_permission with patch.object( doc_class, "_search_user_permission", autospec=True, side_effect=original_search, ) as patched_search_user_permission: self.assertTrue(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '!=', 'none')])) self.assertEqual(patched_search_user_permission.call_count, 1) self.document_txt.action_update_access_rights(access_internal='none') self.assertEqual(self.document_txt.with_user(self.internal_user).user_permission, 'none') self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'none') self.assertFalse(Doc_as_internal.search([('id', '=', shortcut.id)])) with patch.object( doc_class, "_search_user_permission", autospec=True, side_effect=original_search, ) as patched_search_user_permission: self.assertFalse(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '=', 'edit')])) self.assertEqual(patched_search_user_permission.call_count, 2) self.assertFalse(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '=', 'view')])) self.document_txt.sudo().owner_id = self.internal_user self.assertEqual(self.document_txt.with_user(self.internal_user).user_permission, 'edit') self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'edit') self.assertTrue(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '=', 'edit')])) self.assertTrue(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '!=', 'none')])) self.assertFalse(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '=', 'view')])) def test_access_rights_shortcuts_target_accessible_via_link(self): Doc_as_internal_sudo = self.env['documents.document'].with_user(self.internal_user).sudo() self.folder_a.access_internal = 'edit' self.document_txt.folder_id.action_update_access_rights(access_internal='view') self.document_txt.action_update_access_rights( access_internal='none', access_via_link='view', is_access_via_link_hidden=False, ) self.env['documents.access'].create([{ 'document_id': self.document_txt.id, 'partner_id': self.internal_user.partner_id.id, 'last_access_date': fields.Datetime.now(), }]) shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_user_folder_id=str(self.folder_a.id)) self.assertEqual(self.document_txt.with_user(self.internal_user).user_permission, 'view') self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'edit') self.assertTrue(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '=', 'edit')])) self.assertTrue(Doc_as_internal_sudo.search([('id', '=', shortcut.id), ('user_permission', '!=', 'none')])) # We remove the access on the target self.document_txt.action_update_access_rights(access_via_link='none') self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'none') self.assertEqual(shortcut.with_user(self.internal_user).user_permission, 'none') @mute_logger('odoo.addons.base.models.ir_rule') @users('documents@example.com') def test_access_rights_shortcuts_propagation(self): """Test that we update shortcuts if we have edit access on the document""" target = self.env['documents.document'].create({ 'name': 'Target', 'access_internal': 'edit', }) root = self.env['documents.document'].create({ 'name': 'Root', 'type': 'folder', 'access_internal': 'edit', 'children_ids': [Command.create({ 'name': 'A', 'type': 'folder', 'access_internal': 'edit', 'is_access_via_link_hidden': True, 'children_ids': [Command.create({ 'name': 'File 1', 'access_internal': 'edit', }), Command.create({ 'name': 'File 2', 'access_internal': 'view', }), Command.create({ 'name': 'File 3', 'access_internal': 'none', 'access_via_link': 'edit', 'is_access_via_link_hidden': False, }), Command.create({ 'name': 'Sub-folder', 'type': 'folder', 'access_internal': 'edit', 'children_ids': [Command.create({ 'name': 'Sub-file', 'access_internal': 'edit', })] }), Command.create({ 'name': 'Shortcut', 'access_internal': 'edit', 'shortcut_document_id': target.id, })], })], 'owner_id': self.document_manager.id # not False }) file_1 = self.env['documents.document'].search([('id', 'child_of', root.id), ('name', '=', 'File 1')]) file_2 = self.env['documents.document'].search([('id', 'child_of', root.id), ('name', '=', 'File 2')]) file_3 = self.env['documents.document'].search([('id', 'child_of', root.id), ('name', '=', 'File 3')]) shortcut = self.env['documents.document'].search([('id', 'child_of', root.id), ('name', '=', 'Shortcut')]) sub_folder = self.env['documents.document'].search([('id', 'child_of', root.id), ('name', '=', 'Sub-folder')]) sub_file = self.env['documents.document'].search([('id', 'child_of', root.id), ('name', '=', 'Sub-file')]) shortcut_1 = file_1.action_create_shortcut(location_user_folder_id='MY') shortcut_2 = file_2.action_create_shortcut(location_user_folder_id='MY') shortcut_3 = file_3.action_create_shortcut(location_user_folder_id='MY') self.assertEqual(shortcut_1.access_internal, 'edit') self.assertEqual(shortcut_2.access_internal, 'view') self.assertEqual(shortcut_3.access_internal, 'none') # ** sanity check ** # no write access, we should not update it self.assertEqual(file_2.with_user(self.internal_user).user_permission, 'view') self.assertEqual(shortcut_2.with_user(self.internal_user).user_permission, 'view') # we have write access on the target but not on the shortcut # (the shortcut should be updated in "SUDO" in order to keep them synchronized) self.assertEqual(file_3.with_user(self.internal_user).user_permission, 'edit') self.assertEqual(shortcut_3.with_user(self.internal_user).user_permission, 'none') # ****************** root.with_user(self.internal_user).action_update_access_rights(access_via_link='view') self.assertEqual(root.access_via_link, 'view') self.assertEqual(file_1.access_via_link, 'view') self.assertEqual(file_2.access_via_link, 'none') self.assertEqual(file_3.access_via_link, 'view') self.assertEqual(shortcut_1.access_via_link, 'view') self.assertEqual(shortcut_2.access_via_link, 'none') self.assertEqual(shortcut_3.access_via_link, 'view') self.assertEqual(sub_folder.access_via_link, 'view') self.assertEqual(sub_file.access_via_link, 'view') # The access update is done only from the target to the shortcut, # and not from the shortcut to the target self.assertEqual(target.access_via_link, 'none') self.assertEqual(shortcut.access_via_link, 'none') # test the propagation of the members root.with_user(self.internal_user).action_update_access_rights( partners={self.internal_user.partner_id: ('edit', None)}) def get_access(document): return document.access_ids.filtered( lambda a: a.partner_id == self.internal_user.partner_id).role self.assertEqual(get_access(root), 'edit') self.assertEqual(get_access(file_1), 'edit') self.assertEqual(get_access(file_2), False, 'The user can not write on that document') self.assertEqual(get_access(file_3), False) self.assertEqual(get_access(sub_folder), 'edit') self.assertEqual(get_access(sub_file), 'edit') # shortcut always synchronized self.assertEqual(get_access(shortcut_1), 'edit') self.assertEqual(get_access(shortcut_2), False) self.assertEqual(get_access(shortcut_3), False) # The members' update is done only from the target to the shortcut, # and not from the shortcut to the target self.assertEqual(get_access(target), False) self.assertEqual(get_access(shortcut), False) def test_shortcuts_cant_have_children(self): folder_a_shortcut = self.folder_a.action_create_shortcut() with self.assertRaises(ValidationError): self.env['documents.document'].create({'type': 'folder', 'folder_id': folder_a_shortcut.id}) self.folder_b.folder_id = folder_a_shortcut self.assertEqual(self.folder_b.folder_id, self.folder_a, "The shortcut's target should have been used") def test_portal_cant_own_root_documents(self): self.assertFalse(self.folder_a.folder_id) with self.assertRaises(UserError): self.folder_a.owner_id = self.portal_user @mute_logger('odoo.addons.base.models.ir_rule') def test_copy_document_access(self): """ Test that the copy method of Document also copies access rights. """ IN_ONE_DAY = fields.Datetime.now() + datetime.timedelta(days=1) documents = self.document_gif | self.document_txt self.folder_b.access_internal = 'none' self.folder_b.action_update_access_rights(partners={self.internal_user.partner_id: ('view', False)}) # If we have no access on the root folder, copy in "My Drive" copied_document = documents.with_user(self.internal_user).copy() self.assertFalse(copied_document.folder_id) self.assertEqual(copied_document.owner_id, self.internal_user) self.folder_b.action_update_access_rights(partners={self.internal_user.partner_id: ('edit', False)}) self.document_gif.action_update_access_rights(partners={self.internal_user.partner_id: (False, False)}) self.document_gif.access_internal = 'none' with self.assertRaises(AccessError, msg='No access to one of the document'): documents.with_user(self.internal_user).copy() self.document_txt.action_update_access_rights(partners={self.portal_user.partner_id: ('view', IN_ONE_DAY)}) self.document_gif.action_update_access_rights(partners={self.internal_user.partner_id: ('view', False)}) # Copying folders is also possible (documents | self.folder_b).with_user(self.internal_user).copy() gif_copy, txt_copy = documents.with_user(self.internal_user).copy() self.assertEqual((gif_copy | txt_copy).owner_id, self.internal_user) self.assertTrue(gif_copy.attachment_id) gif_copy_access_ids = gif_copy.access_ids self.assertEqual( set((a.partner_id.name, a.role) for a in gif_copy_access_ids), {(self.internal_user.name, False), (self.doc_user.name, 'edit')}, "Existing member and original owner should have access" ) self.assertSetEqual({*gif_copy_access_ids.mapped('expiration_date')}, {False}) txt_copy_access_by_partner = {a.partner_id: (a.role, a.expiration_date) for a in txt_copy.access_ids} self.assertEqual(len(txt_copy_access_by_partner), 3) self.assertEqual(txt_copy_access_by_partner.get(self.portal_user.partner_id), ('view', IN_ONE_DAY)) self.assertEqual(txt_copy_access_by_partner.get(self.doc_user.partner_id), ('edit', False)) self.assertEqual(txt_copy_access_by_partner.get(self.internal_user.partner_id), (False, False)) self.document_txt.action_archive() with self.assertRaises(UserError, msg='Cannot copy document in the Trash'): documents.with_user(self.internal_user).copy() url_document_in_my_folder = self.env['documents.document'].create( {'name': 'url', 'type': 'url', 'url': 'https://www.odoo.com/', 'owner_id': self.internal_user.id}) _, url_copied = (self.document_gif | url_document_in_my_folder).with_user(self.internal_user).copy() self.assertEqual(url_copied.owner_id, self.internal_user) shortcut = self.env['documents.document'].create( {'name': 'shortcut', 'shortcut_document_id': self.document_gif.id, 'owner_id': self.internal_user.id}) # Copying shortcuts is also supported shortcuts = url_document_in_my_folder | shortcut shortcuts.folder_id = self.folder_a with mute_logger('odoo.addons.documents.models.documents_document'): # Creating document(s) as superuser copied_shortcuts = shortcuts.copy() self.assertEqual(copied_shortcuts.folder_id, self.folder_a) # If we have no access on the root folder, create the shortcuts in "My Drive" self.document_gif.owner_id = self.internal_user copied_shortcuts = shortcuts.with_user(self.internal_user).copy() self.assertFalse(copied_shortcuts.folder_id) self.assertEqual(copied_shortcuts.owner_id, self.internal_user) # If we have no access on the root folder, create the folder copy in "My Drive" self.folder_b.folder_id = self.folder_a for access in ('edit', 'view'): self.folder_b.action_update_access_rights(partners={self.internal_user.partner_id: (access, False)}) copied_folder = self.folder_b.with_user(self.internal_user).copy() self.assertFalse(copied_folder.folder_id) self.assertEqual(copied_folder.owner_id, self.internal_user) self.assertEqual(len(copied_folder.children_ids), 6) self.assertNotEqual(copied_folder.children_ids, self.folder_b.children_ids) # If a manager copies a root folder, do not change the owner self.folder_b.folder_id = False self.assertEqual(self.folder_b.owner_id, self.doc_user) self.assertFalse(self.folder_b._is_company_root_folder()) copied_folder = self.folder_b.with_user(self.document_manager).copy() self.assertFalse(copied_folder.folder_id) self.assertEqual(copied_folder.owner_id, self.document_manager) self.folder_b.owner_id = False self.assertTrue(self.folder_b._is_company_root_folder()) copied_folder = self.folder_b.with_user(self.document_manager).copy() self.assertFalse(copied_folder.folder_id) self.assertFalse(copied_folder.owner_id) # The user has the access on the documents, but he can not create pinned folders # Create the copy in "My Drive" self.folder_b.access_internal = 'edit' copied_folder = self.folder_b.with_user(self.internal_user).copy() self.assertFalse(copied_folder.folder_id) self.assertEqual(copied_folder.owner_id, self.internal_user) # Check that portal can not copy in his drive (no drive) self.folder_b.action_update_access_rights(access_via_link = 'edit', partners={self.portal_user.partner_id: ('edit', False)}) with self.assertRaises(ValidationError): self.folder_b.with_user(self.portal_user).copy() @mute_logger('odoo.addons.base.models.ir_rule') def test_deleting_in_non_edit_folder(self): """Check required rights on document and containing folder to archive/unlink. Archiving a document requires being the owner of the document or having edit permission on both the document and its containing folder. """ # Manager creates a folder in their drive and gives editor right to user folder = self.env['documents.document'].with_user(self.document_manager).create({ 'folder_id': False, 'name': 'Folder with User', 'type': 'folder', 'access_ids': [Command.create({'partner_id': self.doc_user.partner_id.id, 'role': 'edit'})], }) # User creates two documents in that folder user_doc_1, user_doc_2, user_doc_3 = user_docs = self.env['documents.document'].with_user(self.doc_user).create([{ 'folder_id': folder.id, 'name': f'User doc #{idx}', 'type': 'binary', } for idx in range(3)]) self.assertEqual(user_docs.owner_id, self.doc_user) # Manager removes user's role on the folder folder.with_user(self.document_manager).action_update_access_rights( partners={self.doc_user.partner_id: (False, None)}, ) self.assertEqual(folder.with_user(self.doc_user).user_permission, 'none') user_doc_1.with_user(self.doc_user).action_archive() self.assertFalse(user_doc_1.active, 'Owner should be able to archive the document') user_doc_1.with_user(self.doc_user).action_unarchive() self.assertTrue(user_doc_1.active, 'Owner should be able to restore the document') user_doc_1.with_user(self.doc_user).unlink() self.assertFalse(user_doc_1.exists(), 'Owner should be able to unlink the document') user_doc_2.with_user(self.document_manager).owner_id = self.document_manager user_doc_2.with_user(self.document_manager).action_update_access_rights( partners={self.doc_user.partner_id: ('edit', None)}, ) self.assertEqual(user_doc_2.with_user(self.doc_user).user_permission, 'edit') with self.assertRaises(UserError, msg="Editor without edit permission on folder shouldn't be able to archive"): user_doc_2.with_user(self.doc_user).action_archive() with self.assertRaises(AccessError, msg="Editor without edit permission on folder shouldn't be able to archive"): user_doc_2.with_user(self.doc_user).active = False with self.assertRaises(UserError, msg="Editor without edit permission on folder shouldn't be able to delete"): user_doc_2.with_user(self.doc_user).unlink() user_doc_2.folder_id.sudo().access_internal = 'none' with self.assertRaises(UserError, msg="Editor with no permission on folder shouldn't be able to delete"): user_doc_2.with_user(self.doc_user).unlink() user_doc_2.folder_id.sudo().access_internal = 'view' with self.assertRaises(UserError, msg="Editor with view permission on folder shouldn't be able to delete"): user_doc_2.with_user(self.doc_user).unlink() user_doc_3.with_user(self.document_manager).owner_id = self.document_manager user_doc_3.with_user(self.document_manager).action_update_access_rights( partners={self.doc_user.partner_id: ('view', None)}, ) self.assertFalse((user_doc_2 | user_doc_3)._filtered_access('unlink')) def test_updating_owner(self): self.assertEqual(self.folder_a_a.owner_id, self.doc_user) self.folder_a.action_update_access_rights(access_internal='edit') self.assertEqual(self.folder_a_a.with_user(self.internal_user).user_permission, 'edit') with self.assertRaises(AccessError): self.folder_a_a.with_user(self.internal_user).owner_id = self.internal_user self.folder_a_a.action_update_access_rights(access_internal='none') self.assertEqual(self.folder_a_a.with_user(self.internal_user).user_permission, 'none') # Add a document in their folder but set their access to viewer. self.document_txt.folder_id = self.folder_a_a self.document_txt.owner_id = self.document_manager self.document_txt.action_update_access_rights(partners={self.doc_user.partner_id: ('view', False)}) self.assertEqual(self.document_txt.with_user(self.doc_user).user_permission, 'view') self.folder_a_a.with_user(self.doc_user).owner_id = self.internal_user self.assertEqual(self.folder_a_a.with_user(self.doc_user).user_permission, 'edit', "Previous owner should have kept edit permission on folder") self.assertEqual(self.document_txt.with_user(self.doc_user).user_permission, 'view', "Previous owner should not have gained rights on folder content") @mute_logger('odoo.addons.base.models.ir_rule') def test_embedded_action(self): """Test embedding and running actions on the right records""" self.folder_a.action_update_access_rights( access_internal="edit", partners={self.portal_user.partner_id: ('edit', False)} ) with self.assertRaises(AccessError): self.env['documents.document'].with_user(self.internal_user).action_folder_embed_action( self.folder_a.id, self.server_action.id) self.server_action.group_ids = self.env.ref('documents.group_documents_manager') with self.assertRaises(UserError): self.env['documents.document'].with_user(self.doc_user).action_folder_embed_action( self.folder_a.id, self.server_action.id) self.server_action.group_ids = self.env.ref('base.group_user') self.env['documents.document'].with_user(self.doc_user).action_folder_embed_action( self.folder_a.id, self.server_action.id) doc = self.env['documents.document'].create({'name': 'A request', 'folder_id': self.folder_a.id}) embedded_action = doc.available_embedded_actions_ids self.assertEqual(len(embedded_action), 1) doc_in_context = self.env['documents.document'].with_context( active_model='documents.document', active_id=doc.id) with self.assertRaises(AccessError): doc_in_context.with_user(self.portal_user).action_execute_embedded_action(embedded_action.id) doc_in_context.with_user(self.internal_user).action_execute_embedded_action(embedded_action.id) with self.assertRaises(UserError): doc_in_context.with_context( active_ids=(doc | self.folder_a).ids, ).with_user(self.internal_user).action_execute_embedded_action(embedded_action.id) doc.action_update_access_rights(access_internal='none') with self.assertRaises(AccessError): doc_in_context.with_context( active_id=doc.id ).with_user(self.internal_user).action_execute_embedded_action(embedded_action.id) @mute_logger('odoo.addons.base.models.ir_rule') def test_embedded_action_shortcut_folder(self): """Test embedding and running actions on the right records""" self.folder_a.action_update_access_rights( access_internal="edit", partners={self.portal_user.partner_id: ('edit', False)} ) doc = self.env['documents.document'].create({'name': 'A request', 'folder_id': self.folder_a.id}) self.assertFalse(doc.available_embedded_actions_ids) folder_a_shortcut = self.folder_a.action_create_shortcut() self.assertFalse(self.folder_a._get_folder_embedded_actions([self.folder_a.id]).get(self.folder_a.id)) self.assertFalse(self.folder_a._get_folder_embedded_actions([folder_a_shortcut.id]).get(folder_a_shortcut.id)) # From here, same as `test_embedded_action` but using the shortcut instead with self.assertRaises(AccessError): self.env['documents.document'].with_user(self.internal_user).action_folder_embed_action( folder_a_shortcut.id, self.server_action.id) self.server_action.group_ids = self.env.ref('documents.group_documents_manager') with self.assertRaises(UserError): self.env['documents.document'].with_user(self.doc_user).action_folder_embed_action( folder_a_shortcut.id, self.server_action.id) self.server_action.group_ids = self.env.ref('base.group_user') self.env['documents.document'].with_user(self.doc_user).action_folder_embed_action( folder_a_shortcut.id, self.server_action.id) self.assertEqual(len(doc.available_embedded_actions_ids), 1) self.assertTrue(self.folder_a._get_folder_embedded_actions([self.folder_a.id])[self.folder_a.id]) self.assertEqual(self.folder_a._get_folder_embedded_actions([folder_a_shortcut.id])[folder_a_shortcut.id], self.folder_a._get_folder_embedded_actions([self.folder_a.id])[self.folder_a.id]) self.folder_a.action_update_access_rights(access_internal="view") self.internal_user.group_ids |= self.env.ref('documents.group_documents_user') document = self.document_gif.with_user(self.internal_user) document.sudo().access_internal = 'edit' action = self.env['ir.actions.server'].create({ 'name': 'Test Action', 'model_id': self.env['ir.model']._get_id('documents.document'), 'update_path': 'folder_id', 'usage': 'documents_embedded', 'state': 'object_write', 'resource_ref': f'documents.document,{self.folder_a.id}', }).with_user(self.internal_user) # Check that we need write access on the folder to pin it self.assertEqual(document.folder_id.user_permission, 'view') with self.assertRaises(AccessError): document.env['documents.document'].action_folder_embed_action(document.folder_id.id, action.id) document.folder_id.sudo().access_internal = 'edit' with RecordCapturer(self.env['ir.embedded.actions'], []) as capturer: document.env['documents.document'].action_folder_embed_action(document.folder_id.id, action.id) embedded = capturer.records self.assertEqual(len(embedded), 1) # Try to move an existing embedded action self.assertEqual(self.folder_a.with_user(self.internal_user).user_permission, 'view') self.assertEqual(self.folder_b.with_user(self.internal_user).user_permission, 'edit') with self.assertRaises(AccessError): embedded.with_user(self.internal_user).parent_res_id = self.folder_a.id embedded.parent_res_id = self.folder_a.id with self.assertRaises(AccessError): embedded.with_user(self.internal_user).parent_res_id = self.folder_b.id def test_groupless_embedded_action_availability(self): """ Ensure that an embedded action which should otherwise be visible to a given document record remains visible in the case where it has `groups_ids=[]`. """ embedded_action = self.env['ir.embedded.actions'].create({ 'name': 'public action', 'parent_action_id': self.env.ref('documents.document_action').id, 'action_id': self.server_action.id, 'parent_res_model': 'documents.document', 'groups_ids': [Command.clear()], 'parent_res_id': self.document_txt.folder_id.id, }) self.assertIn(embedded_action, self.document_txt.available_embedded_actions_ids) @freeze_time('2025-02-26 15:02:00') def test_log_owner_access_when_passing_access_ids(self): doc = self.env['documents.document'].with_user(self.doc_user).create({ 'access_ids': [Command.create({'partner_id': self.document_manager.partner_id.id, 'role': 'edit'})]}) self.assertEqual( set(doc.access_ids.mapped(lambda a: (a.partner_id, a.role, a.last_access_date))), { (self.document_manager.partner_id, 'edit', False), (self.doc_user.partner_id, False, fields.Datetime.now()) } ) doc = self.env['documents.document'].with_user(self.doc_user).create({'access_ids': False}) self.assertEqual(set(doc.access_ids.mapped(lambda a: (a.partner_id, a.role, a.last_access_date))), {(self.doc_user.partner_id, False, fields.Datetime.now())}) @users('documents@example.com') def test_embedded_actions_unembed(self): """Check that actions can be un-embedded as records may exist with/without these groups. Groups defined on embedded actions are not supported while those on the server actions are (tested in test_embedded_action).""" folder = self.document_txt.folder_id self.assertFalse(self.document_txt.available_embedded_actions_ids) self.document_txt.invalidate_recordset(['available_embedded_actions_ids']) embedded_action_vals = { 'name': 'public action', 'parent_action_id': self.env.ref('documents.document_action').id, 'action_id': self.server_action.id, 'parent_res_model': 'documents.document', 'parent_res_id': folder.id, } embedded_action_with_group = self.env['ir.embedded.actions'].create({ **embedded_action_vals, 'groups_ids': [self.env.ref('documents.group_documents_manager').id], }) self.assertIn(embedded_action_with_group, self.document_txt.available_embedded_actions_ids) self.document_txt.invalidate_recordset(['available_embedded_actions_ids']) self.env['documents.document'].action_folder_embed_action(folder.id, self.server_action.id) self.assertFalse(self.document_txt.available_embedded_actions_ids, "Embedded action with group should have been removed.") self.document_txt.invalidate_recordset(['available_embedded_actions_ids']) self.env['documents.document'].action_folder_embed_action(folder.id, self.server_action.id) self.assertTrue(self.document_txt.available_embedded_actions_ids, "Embedded action should have been created.") self.document_txt.available_embedded_actions_ids.groups_ids = False self.env['documents.document'].action_folder_embed_action(folder.id, self.server_action.id) self.assertFalse(self.document_txt.available_embedded_actions_ids, "Embedded action without group should have been removed.") self.document_txt.invalidate_recordset(['available_embedded_actions_ids']) self.env['ir.embedded.actions'].create([{ **embedded_action_vals, 'groups_ids': [self.env.ref('documents.group_documents_manager').id], }, embedded_action_vals ]) self.assertTrue(self.document_txt.available_embedded_actions_ids) self.document_txt.invalidate_recordset(['available_embedded_actions_ids']) self.env['documents.document'].action_folder_embed_action(folder.id, self.server_action.id) self.assertFalse(self.document_txt.available_embedded_actions_ids, "Embedded action with and without group should have been removed.") def test_restricted_folder_dependant_fields(self): """Check that reading fields depending on restricted folder doesn't raise (access) error.""" self.folder_b.access_internal = 'none' self.assertEqual(self.folder_b.with_user(self.internal_user).user_permission, 'none') self.assertFalse(self.env['documents.document'].with_user(self.internal_user)._get_folder_embedded_actions(self.folder_b.id)) self.document_gif.access_internal = 'view' self.assertEqual(self.document_gif.with_user(self.internal_user).user_permission, 'view') self.document_gif.invalidate_recordset() # cache pollution self.assertFalse(self.document_gif.with_user(self.internal_user).available_embedded_actions_ids) # Private method now returns actions because there are accessible child(ren) self.env['documents.document'].with_user(self.internal_user)._get_folder_embedded_actions(self.folder_b.id) with self.assertRaises(UserError): # Public method doesn't need to, actions are only embeddable by folder editors. self.env['documents.document'].with_user(self.internal_user).get_documents_actions(self.folder_b.id) folder_b_embedded_server_action_ids = { server_action['id'] for server_action in self.folder_b.action_folder_embed_action(self.folder_b.id, self.server_action.id) if server_action['is_embedded'] } embedded_action_folder_b = self.document_gif.with_user(self.internal_user).available_embedded_actions_ids self.assertIn(embedded_action_folder_b.action_id.id, folder_b_embedded_server_action_ids) def test_tracking_creation_on_access_rights_changes(self): self.folder_a_a.unlink() # 10 sub folders sub_folder_ids = self.env['documents.document'].create([ { 'type': 'folder', 'name': f'sub folder A-{letter}', 'folder_id': self.folder_a.id, } for letter in ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'j') ]) # 10 sub sub folders = 100 sub sub folders in total for sub_folder in sub_folder_ids: self.env['documents.document'].create([ { 'type': 'folder', 'name': f'{sub_folder.name}-{count}', 'folder_id': sub_folder.id, 'children_ids': [Command.create({ 'name': f'File {count}', }) for count in range(10)] # 10 files in each sub sub folders = 1000 files } for count in range(10) ]) self.assertEqual(len(self.folder_a.children_ids), 10) for child in self.folder_a.children_ids: self.assertEqual(len(child.children_ids), 10) self.assertEqual(self.folder_a.access_internal, 'view') def _get_mail_messages(doc_ids): return self.env['mail.message'].search([ ('res_id', 'in', doc_ids), ('tracking_value_ids', '!=', False) ]) folder_a_d = sub_folder_ids[3] self.assertEqual(folder_a_d.access_internal, 'view') folder_a_d.action_update_access_rights( access_internal='edit', partners={self.portal_user.partner_id.id: ('view', False)}) with self.enter_registry_test_mode(): self.env.ref('documents.ir_cron_documents_access_tracking').method_direct_trigger() folder_a_d_message_ids = _get_mail_messages(folder_a_d.ids + folder_a_d.children_ids.ids) self.assertTrue(folder_a_d_message_ids.exists()) for message in folder_a_d_message_ids: self.assertTracking(message, [ ('access_internal', 'char', 'Viewer', 'Editor') ], strict=True) self.assertIn('Portal user has gained access as\nViewer', html2plaintext(message.body)) self.folder_a.action_update_access_rights( access_internal='edit', access_via_link='view', is_access_via_link_hidden=True, partners={self.portal_user.partner_id.id: ('edit', False)}) while self.env['documents.access.tracking'].search_count([]) > 0: with self.enter_registry_test_mode(): self.env.ref('documents.ir_cron_documents_access_tracking').method_direct_trigger() other_folders_ids = sub_folder_ids - folder_a_d message_ids = _get_mail_messages(other_folders_ids.ids + other_folders_ids.children_ids.ids) self.assertTrue(message_ids.exists()) for message in message_ids: self.assertTracking(message, [ ('access_internal', 'char', 'Viewer', 'Editor'), ('access_via_link', 'char', 'None', 'Viewer'), ], strict=True) self.assertIn('Portal user has gained access as\nEditor', html2plaintext(message.body)) folder_a_d_latest_message_ids = _get_mail_messages(folder_a_d.ids + folder_a_d.children_ids.ids).filtered( lambda message: message.id not in folder_a_d_message_ids.ids ) for message in folder_a_d_latest_message_ids: self.assertTracking(message, [ ('access_via_link', 'char', 'None', 'Viewer'), # 'is_access_via_link_hidden' is not propagated ], strict=True) self.assertIn('Portal user rights changed from\nViewer\nto\nEditor', html2plaintext(message.body)) def test_members_access_on_move_documents(self): self.document_txt.action_update_access_rights(partners={self.internal_user.partner_id.id: ('view', False)}) self.env['documents.access'].create({ 'document_id': self.folder_a.id, 'partner_id': self.internal_user.partner_id.id, 'last_access_date': fields.Datetime.now(), }) self.assertIn( (self.internal_user.partner_id, 'view'), self.document_txt.access_ids.mapped(lambda a: (a.partner_id, a.role))) self.assertIn( (self.internal_user.partner_id, False), self.folder_a.access_ids.mapped(lambda a: (a.partner_id, a.role))) self.document_txt.folder_id = self.folder_a.id self.assertIn( (self.internal_user.partner_id, 'view'), self.document_txt.access_ids.mapped(lambda a: (a.partner_id, a.role))) def test_members_invitation(self): Access = self.env['documents.access'] internal_access = Access.create({ 'document_id': self.folder_a.id, 'partner_id': self.internal_user.partner_id.id, 'last_access_date': fields.Datetime.now(), 'role': 'view', }) with self.assertRaises(UserError): internal_access._get_member_signup_token() portal_access = Access.create({ 'document_id': self.folder_a.id, 'partner_id': self.portal_user.partner_id.id, 'last_access_date': fields.Datetime.now(), 'role': 'view', }) with self.assertRaises(UserError): portal_access._get_member_signup_token() public_access = Access.create({ 'document_id': self.folder_a.id, 'partner_id': self.env["res.partner"].create({'name': 'Test'}).id, 'last_access_date': fields.Datetime.now(), 'role': 'view', }) token = public_access._get_member_signup_token() self.assertEqual(Access._get_member_from_token(public_access.id, token), public_access) self.assertNotEqual(Access._get_member_from_token(public_access.id, 'invalid' + token), public_access) public_access.expiration_date = fields.Datetime.now() + datetime.timedelta(days=1) token = public_access._get_member_signup_token() self.assertEqual(Access._get_member_from_token(public_access.id, token), public_access) # use an old token token = public_access._get_member_signup_token() public_access.expiration_date = fields.Datetime.now() - datetime.timedelta(days=1) self.assertFalse(Access._get_member_from_token(public_access.id, token)) public_access = Access.create({ 'document_id': self.folder_a.id, 'partner_id': self.env["res.partner"].create({'name': 'Test'}).id, 'last_access_date': fields.Datetime.now(), }) with self.assertRaises(UserError): public_access._get_member_signup_token() def test_permissions_internal_propagation_on_folder_moves(self): not_secret_folder = self.env['documents.document'].create({ 'name': 'Not Secret Folder', 'type': 'folder', 'access_internal': 'edit', 'children_ids': [ Command.create({ 'name': 'Secret Folder', 'type': 'folder', 'access_internal': 'none', }), ], 'owner_id': self.document_manager.id # not odoobot }) secret_folder = not_secret_folder.children_ids[0] self.assertEqual(secret_folder.with_user(self.internal_user).user_permission, 'none') none = self.env['documents.document'].with_user(self.internal_user).create({ 'name': 'none', 'type': 'folder', 'access_internal': 'none', }) editor = self.env['documents.document'].with_user(self.internal_user).create({ 'name': 'editor', 'type': 'folder', 'access_internal': 'edit', }) not_secret_folder.with_user(self.internal_user).action_move_folder(target=str(none.id)) none.with_user(self.internal_user).action_move_folder(target=str(editor.id)) with self.assertRaises(AccessError): secret_folder.with_user(self.internal_user).check_access('read') self.assertEqual(secret_folder.access_internal, 'none') def test_permissions_member_and_owner_propagation_on_folder_moves(self): member_folder = self.env['documents.document'].create({ 'name': 'Member Folder', 'type': 'folder', 'access_internal': 'none', 'children_ids': [ Command.create({ 'name': 'Secret Folder', 'type': 'folder', 'access_internal': 'none', 'access_ids': False, }), ], 'access_ids': [Command.create({'partner_id': self.internal_user.partner_id.id, 'role': 'edit'})], 'owner_id': self.document_manager.id # not odoobot }) secret_folder = member_folder.children_ids[0] self.assertEqual(secret_folder.with_user(self.doc_user).user_permission, 'none') none = self.env['documents.document'].with_user(self.internal_user).create({ 'name': 'none', 'type': 'folder', 'access_internal': 'none', }) doc_user_editor = self.env['documents.document'].with_user(self.internal_user).create({ 'name': 'editor', 'type': 'folder', 'access_internal': 'none', 'access_ids': [Command.create({'partner_id': self.doc_user.partner_id.id, 'role': 'edit'})] }) member_folder.with_user(self.internal_user).action_move_folder(target=str(none.id)) none.with_user(self.internal_user).action_move_folder(target=str(doc_user_editor.id)) none.with_user(self.doc_user).check_access('read') member_folder.with_user(self.doc_user).check_access("read") self.assertIn(self.doc_user.partner_id, member_folder.access_ids.partner_id) with self.assertRaises(AccessError): secret_folder.with_user(self.internal_user).check_access('read') with self.assertRaises(AccessError): secret_folder.with_user(self.doc_user).check_access('read') self.assertNotIn(self.internal_user.partner_id, secret_folder.access_ids.partner_id) self.assertNotIn(self.doc_user.partner_id, secret_folder.access_ids.partner_id) @users('documents@example.com') def test_owner_edition(self): """Check that with edit permission, users can change the owner of a document he owns or without owners.""" document = self.env['documents.document'].create({'name': 'test'}) self.assertEqual(document.owner_id, self.env.user) self.assertEqual(document.user_folder_id, 'MY') document.owner_id = False self.assertEqual(document.user_permission, 'edit', 'Removing the owner gives him edit permission') self.assertEqual(document.user_folder_id, 'COMPANY') document.owner_id = self.env.user self.assertEqual(document.user_folder_id, 'MY') document.owner_id = False document.owner_id = self.document_manager with self.assertRaises(AccessError, msg='You cannot change the owner of documents you do not own.'): document.owner_id = self.env.user