import datetime from odoo import Command, fields from odoo.addons.documents.tests.test_documents_common import TransactionCaseDocuments from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools import mute_logger class TestDocumentsAccess(TransactionCaseDocuments): @mute_logger('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. 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), 1) self.assertEqual(folder_a4.access_ids.partner_id, self.internal_user.partner_id) 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._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.groups_id |= 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(5): 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(5): 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(5): 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(5): 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(7): 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(4): 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(4): 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(5): 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(4): 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)) 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'}) 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._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)}) def test_action_move_documents(self): """Check that documents can be moved to new location and have their rights updated.""" self.folder_b.write({ 'access_internal': 'none', 'access_via_link': 'none', }) self.folder_a.write({'access_via_link': 'view'}) self.folder_a_a.action_move_documents(folder_id=self.folder_b.id) self.assertEqual(self.folder_a_a.folder_id, self.folder_b) self.folder_b.action_move_documents(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.document_gif.folder_id = False shortcut = self.folder_b.action_create_shortcut(False) self.document_gif.action_move_documents(shortcut.id) self.assertEqual(self.document_gif.folder_id, self.folder_b) doc_shortcut = self.document_gif.action_create_shortcut(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(False) self.assertEqual(shortcut.shortcut_document_id, self.folder_b) @mute_logger('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.env.ref('base.user_root').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.env.ref('base.user_root').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 """ odoobot = self.env.ref('base.user_root') self.assertFalse(self.folder_a.folder_id) self.folder_a.owner_id = odoobot 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 }) # Managers can unpin by moving to another odoobot folder self.folder_b.owner_id = odoobot self.folder_a.with_user(self.document_manager).folder_id = self.folder_b self.assertFalse(self.folder_a.is_pinned_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.""" odoobot = self.env.ref('base.user_root') folder = self.env['documents.document'].create({ 'folder_id': False, 'name': 'folder', 'owner_id': odoobot.id, 'type': 'folder', }) self.assertTrue(folder.is_pinned_folder) with self.assertRaises(AccessError): self.env['documents.document'].with_user(self.internal_user).create({ 'folder_id': False, 'name': 'folder', 'owner_id': odoobot.id, '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.""" def set_company_root(document, pin=True): if pin: document.write({'owner_id': self.env.ref('base.user_root').id, 'folder_id': False}) else: document.owner_id = self.env.user odoobot = self.env.ref('base.user_root') self.assertFalse(self.folder_a.folder_id) self.folder_a.owner_id = odoobot set_company_root(self.folder_a) self.folder_a.action_update_access_rights(partners={self.internal_user.partner_id.id: ('edit', False)}) self._assert_raises_check_access_rule(self.folder_a.with_user(self.internal_user), 'write') self.folder_b.owner_id = odoobot set_company_root(self.folder_a) set_company_root(self.folder_a.with_user(self.document_manager), False) self.folder_a.with_user(self.document_manager).folder_id = self.folder_b self.folder_a.with_user(self.internal_user).check_access('write') with self.assertRaises(AccessError): set_company_root(self.folder_a.with_user(self.internal_user)) # with SUDO, a normal user can unpin a folder set_company_root(self.folder_a.with_user(self.internal_user).sudo(), False) # manager can do what they want with an odoobot folder set_company_root(self.folder_a.with_user(self.document_manager)) @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 OdooBot-owned 2nd level folder 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_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') document_txt_private = self.document_txt.copy() 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() @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.action_move_documents(self.folder_a.id) self.folder_a.owner_id = self.env.ref('base.user_root') 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_folder_id=self.folder_a.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). # Access via access_internal shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_folder_id=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(shortcut.exists()) # 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_folder_id=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(shortcut.exists()) # Access via ownership self.document_txt.owner_id = self.internal_user shortcut = self.document_txt.with_user(self.internal_user).action_create_shortcut( location_folder_id=self.folder_a.id ) self.assertEqual(shortcut.owner_id, self.internal_user) self.document_txt.owner_id = self.document_manager self.assertFalse(shortcut.exists()) @mute_logger('odoo.addons.base.models.ir_rule') 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 odoobot }) 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(False) shortcut_2 = file_2.action_create_shortcut(False) shortcut_3 = file_3.action_create_shortcut(False) 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}) with self.assertRaises(UserError): self.folder_b.folder_id = folder_a_shortcut # Note that the result of moving into a shortcut is tested in `test_action_move_documents` 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 copy access right. """ 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)}) with self.assertRaises(AccessError, msg='No "edit" permission on the parent folder'): documents.with_user(self.internal_user).copy() 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.assertTrue(gif_copy.attachment_id) gif_copy_access_ids = gif_copy.access_ids self.assertEqual(gif_copy_access_ids.partner_id, self.internal_user.partner_id) self.assertEqual(gif_copy_access_ids.role, 'view') self.assertFalse(gif_copy_access_ids.expiration_date) 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), 2) 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.internal_user.partner_id), ('edit', 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.ftprotech.in/', 'owner_id': self.internal_user.id}) _, url_copied = (self.document_gif | url_document_in_my_folder).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 (url_document_in_my_folder | shortcut).copy() 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.with_user(self.doc_user).owner_id = self.internal_user @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)} ) server_action = self.env.ref('documents.ir_actions_server_tag_add_validated') with self.assertRaises(AccessError): self.env['documents.document'].with_user(self.internal_user).action_folder_embed_action( self.folder_a.id, server_action.id) self.env['documents.document'].with_user(self.doc_user).action_folder_embed_action( self.folder_a.id, 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)