Files
odoo_source/addons/knowledge/tests/common.py
T
Adrien Schoffeniels ebddc1484b [FIX] knowledge: fix inheritance of internal permissions
Description of the issue:
=========================

Suppose we have 3 articles with the following structure:

root
   L child
         L grand child

When restricting the internal permission in 'child', the inherited
permission parent id and the inherited permission of 'grand child'
are not updated, and the share panel shows that the internal
permission of grand child is based on 'root'.
The 2 fields of grand child (and of any other child of 'child' at any
depth) should be recomputed, and the share panel should show that
the internal permission of these children is based on 'child'.

Solution:
=========
Add 'parent_id.inherited_permission_parent_id' as field on which
'_compute_inherited_permission' depends on, so that 'inherited
permission' and 'inherited permission parent id' of the children of
an article are recomputed when the internal permission of this
article changes.

The scenario described in the issue description has been added to the
tests. One now ensures that if an article gets its internal
permission downgraded (either to 'read' or 'none'), the child and the
grand-child of this article have their inherited permission parent id
equal to the article and their inherited permission equal to the
internal permission of the article.

Task-2866950

X-original-commit: ea2d1b64c5f30c6d0e6a704b4f09c43084371b62
Part-of: odoo/odoo#94834
2022-06-29 12:19:53 +02:00

405 lines
16 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.mail.tests.common import MailCommon, mail_new_test_user
from odoo.tools import mute_logger
class KnowledgeCommon(MailCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._activate_multi_company()
cls.user_portal = cls._create_portal_user()
cls.partner_portal = cls.user_portal.partner_id
cls.user_employee_manager = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
country_id=cls.env.ref('base.be').id,
groups='base.group_user',
login='employee_manager',
name='Evelyne Employee',
notification_type='inbox',
signature='--\nEvelyne'
)
cls.partner_employee_manager = cls.user_employee_manager.partner_id
cls.user_employee2 = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
country_id=cls.env.ref('base.be').id,
groups='base.group_user',
login='employee2',
name='Eglantine Employee',
notification_type='inbox',
signature='--\nEglantine'
)
cls.partner_employee2 = cls.user_employee2.partner_id
cls.user_public = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
groups='base.group_public',
login='user_public',
name='Public Anonymous',
)
cls.partner_public = cls.user_public.partner_id
cls.customer = cls.env['res.partner'].create({
'country_id': cls.env.ref('base.be').id,
'email': 'corentine@test.example.com',
'mobile': '+32455001122',
'name': 'Corentine Customer',
'phone': '+32455334455',
})
def assertMembers(self, articles, exp_internal_permission, exp_partners_permissions, msg=None):
""" Custom assert for members, to ease writing tests. Check state of
members on articles, as sudo to avoid ACLs-based misunderstanding.
:param internal_permission: global internal permission of articles;
:param partners_permission: dict of pid: permission that are members
expected to be on articles;
"""
for article in articles.sudo():
self.assertEqual(article.internal_permission, exp_internal_permission)
self.assertEqual(
dict(
(member.partner_id, member.permission)
for member in article.article_member_ids
),
exp_partners_permissions,
msg
)
def assertSortedSequence(self, articles):
""" Assert that the articles are properly sorted according to their sequence
number
:param articles (Model<knowledge.article>): Recordset of knowledge.article
"""
for k in range(len(articles) - 1):
self.assertTrue(
articles[k].sequence <= articles[k + 1].sequence,
f'Article sequence issue: {articles[k].name} ({articles[k].sequence}) which is not <= than {articles[k + 1].name} ({articles[k + 1].sequence})')
def _create_private_article(self, name, target_user=None):
""" Due to membership model constraints, create test records as sudo
and return a record in current user environment. Creation itself is
not tested here. """
target_user = self.env.user if target_user is None else target_user
if target_user:
vals = {
'article_member_ids': [(0, 0, {
'partner_id': target_user.partner_id.id,
'permission': 'write',
})],
}
vals.update({
'internal_permission': 'none',
'name': name,
})
return self.env['knowledge.article'].sudo().create(vals).with_user(self.env.user)
class KnowledgeCommonWData(KnowledgeCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
with mute_logger('odoo.models.unlink'):
cls.env['knowledge.article'].search([]).unlink()
# - Private seq=997 private none (manager-w+)
# - Child1 seq=0 " "
# - Shared seq=998 shared none (admin-w+,employee-r+,manager-r+)
# - Child1 seq=0 " " (employee-w+)
# - Playground seq=999 workspace w+
# - Child1 seq=0 " "
# - Child2 seq=1 " "
cls._base_sequence = 999
cls.article_workspace = cls.env['knowledge.article'].create(
{'internal_permission': 'write',
'favorite_ids': [(0, 0, {'sequence': 1,
'user_id': cls.user_admin.id
}),
],
'name': 'Playground',
'sequence': cls._base_sequence,
}
)
cls.workspace_children = cls.env['knowledge.article'].create([
{'name': 'Playground Child1',
'parent_id': cls.article_workspace.id,
},
{'name': 'Playground Child2',
'parent_id': cls.article_workspace.id,
},
])
cls.article_shared = cls.env['knowledge.article'].create(
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_admin.id,
'permission': 'write',
}),
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'read',
}),
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'read',
}),
],
'internal_permission': 'none',
'name': 'Shared',
'sequence': cls._base_sequence - 1,
}
)
cls.shared_children = cls.env['knowledge.article'].create([
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'write',
}),
],
'internal_permission': False,
'name': 'Shared Child1',
'parent_id': cls.article_shared.id,
}
])
cls.article_private_manager = cls.env['knowledge.article'].create(
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'write',
}),
],
'internal_permission': 'none',
'name': 'Private',
'sequence': cls._base_sequence - 2,
}
)
cls.private_children = cls.env['knowledge.article'].create([
{'internal_permission': False,
'name': 'Private Child1',
'parent_id': cls.article_private_manager.id,
}
])
cls.env.flush_all()
class KnowledgeArticlePermissionsCase(KnowledgeCommon):
""" Specific test class to test permission management, inheritance and
computation on article model, based on both article and member permissions.
This does not really test ACLs, more the internals of permissions that
are then used in ACLs. """
@classmethod
def setUpClass(cls):
super().setUpClass()
with mute_logger('odoo.models.unlink'):
cls.env['knowledge.article'].search([]).unlink()
# ------------------------------------------------------------
# Perm (" = inherited) + exceptions
# WRITABLE ROOT write
# - Community " (w)
# - Members " (w) (-employee-read)
# - Readonly read (+manager-write)
# - Writable " (w) (+portal-read)
# - Writable child " (w)
# - Child "
# - Nyarlathotep DESYNC read, manager-write
# - Child "
# READABLE ROOT read (+manager-write)
# - TTRPG " (r)
# - OpenCthulhu write (+portal-read)
# - MansionsOfTerror DESYNC none, employee-write, manager-read
# - Child "
# - OpenParanoïa write (-employee-read, +portal-read)
# - Proprietary " (r)
# - Secret none
# - Child "
# - Board Games " (r)
# SHARED ROOT none (+manager-write, +employee-read)
# PRIVATE ROOT none (+employee-write)
#
# ------------------------------------------------------------
cls.article_roots = cls.env['knowledge.article'].create([
{'name': 'Writable Root',
},
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'write',
}),
], # ensure at least one write access
'internal_permission': 'read',
'name': 'Readable Root',
},
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'write',
}),
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'read',
}),
],
'internal_permission': 'none',
'favorite_ids': [
(0, 0, {'user_id': cls.user_employee_manager.id}),
(0, 0, {'user_id': cls.user_employee.id})
],
'name': 'Shared Root',
},
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'write',
}),
],
'internal_permission': 'none',
'name': 'Private Root',
}
])
cls.article_headers = cls.env['knowledge.article'].create([
# writable
{'name': 'Write-Community',
'parent_id': cls.article_roots[0].id,
},
# readable
{'name': 'Read-TTRPG',
'parent_id': cls.article_roots[1].id,
},
{'name': 'Read-Board Games',
'parent_id': cls.article_roots[1].id,
},
])
# Under Write internal permission
cls.article_write_contents = cls.env['knowledge.article'].create([
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'read',
}),
],
'favorite_ids': [
(0, 0, {'user_id': cls.user_admin.id}),
(0, 0, {'user_id': cls.user_employee.id})
],
'name': 'Members Subarticle',
'parent_id': cls.article_headers[0].id,
},
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'write',
}),
], # ensure at least one write access
'internal_permission': 'read',
'name': 'Readonly Subarticle',
'parent_id': cls.article_headers[0].id,
},
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_portal.id,
'permission': 'read',
}),
],
'body': '<p>Writable Subarticle through inheritance</p>',
'favorite_ids': [
(0, 0, {'user_id': cls.user_admin.id}),
(0, 0, {'user_id': cls.user_employee.id}),
(0, 0, {'user_id': cls.user_portal.id}),
],
'name': 'Writable Subarticle through inheritance',
'parent_id': cls.article_headers[0].id,
},
])
cls.article_write_contents_children = cls.env['knowledge.article'].create([
{'name': 'Child of writable through inheritance',
'parent_id': cls.article_write_contents[2].id,
},
])
cls.article_write_contents_children += cls.env['knowledge.article'].create([
{'name': 'Child of child of writable through inheritance',
'parent_id': cls.article_write_contents_children[0].id,
},
])
cls.article_write_desync = cls.env['knowledge.article'].create([
# Community/Writable
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'write',
}),
],
'internal_permission': 'read',
'is_desynchronized': True,
'name': 'Desync Nyarlathotep',
'parent_id': cls.article_write_contents[2].id,
},
])
cls.article_write_desync += cls.env['knowledge.article'].create([
{'name': 'Childof Desync Nyarlathotep',
'parent_id': cls.article_write_desync[0].id,
},
])
# Under Read internal permission
cls.article_read_contents = cls.env['knowledge.article'].create([
# TTRPG
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_portal.id,
'permission': 'read',
}),
],
'internal_permission': 'write',
'name': 'OpenCthulhu',
'parent_id': cls.article_headers[1].id,
},
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_portal.id,
'permission': 'read',
}),
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'read',
}),
],
'internal_permission': 'write',
'name': 'Open Paranoïa',
'parent_id': cls.article_headers[1].id,
},
{'name': 'Proprietary RPGs',
'parent_id': cls.article_headers[1].id,
},
{'internal_permission': 'none',
'name': 'Secret RPGs',
'parent_id': cls.article_headers[1].id,
},
])
cls.article_read_contents_children = cls.env['knowledge.article'].create([
{'name': 'Child of Secret RPGs',
'parent_id': cls.article_read_contents[3].id,
},
])
cls.article_read_desync = cls.env['knowledge.article'].create([
# Read/TTRPG: Open Cthulhu
{'article_member_ids': [
(0, 0, {'partner_id': cls.partner_employee.id,
'permission': 'write',
}),
(0, 0, {'partner_id': cls.partner_employee_manager.id,
'permission': 'read',
}),
],
'internal_permission': 'none',
'is_desynchronized': True,
'name': 'Mansions of Terror',
'parent_id': cls.article_read_contents[0].id,
},
])
cls.article_read_desync += cls.env['knowledge.article'].create([
{'name': 'Childof Desync Mansions',
'parent_id': cls.article_read_desync[0].id,
},
])
cls.articles_all = cls.article_roots + cls.article_headers + \
cls.article_write_contents + cls.article_write_contents_children + \
cls.article_read_contents + cls.article_read_contents_children + \
cls.article_write_desync + cls.article_read_desync
cls.env.flush_all()