You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
9.7 KiB

  1. ###################################################################################
  2. #
  3. # Copyright (c) 2017-2019 MuK IT GmbH.
  4. #
  5. # This file is part of MuK Security
  6. # (see https://mukit.at).
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Lesser General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ###################################################################################
  22. import logging
  23. from collections import defaultdict
  24. from odoo import _, models, api, fields, SUPERUSER_ID
  25. from odoo.exceptions import AccessError
  26. from odoo.osv import expression
  27. from odoo.addons.muk_security.tools.security import NoSecurityUid
  28. _logger = logging.getLogger(__name__)
  29. class AccessGroupsModel(models.AbstractModel):
  30. _name = 'muk_security.mixins.access_groups'
  31. _description = "Group Access Mixin"
  32. _inherit = 'muk_security.mixins.access_rights'
  33. # If set the group fields are restricted by the access group
  34. _access_groups_fields = None
  35. # If set the group fields are recomputed as super administrator
  36. _access_groups_sudo = False
  37. # Set it to True to enforced security even if no group has been set
  38. _access_groups_strict = False
  39. # Set it to True to let the non strict mode check for existing groups per mode
  40. _access_groups_mode = False
  41. #----------------------------------------------------------
  42. # Datebase
  43. #----------------------------------------------------------
  44. @api.model
  45. def _add_magic_fields(self):
  46. super(AccessGroupsModel, self)._add_magic_fields()
  47. def add(name, field):
  48. if name not in self._fields:
  49. self._add_field(name, field)
  50. add('groups', fields.Many2many(
  51. _module=self._module,
  52. comodel_name='muk_security.access_groups',
  53. relation='%s_groups_rel' % (self._table),
  54. column1='aid',
  55. column2='gid',
  56. string="Groups",
  57. automatic=True,
  58. groups=self._access_groups_fields))
  59. add('complete_groups', fields.Many2many(
  60. _module=self._module,
  61. comodel_name='muk_security.access_groups',
  62. relation='%s_complete_groups_rel' % (self._table),
  63. column1='aid',
  64. column2='gid',
  65. string="Complete Groups",
  66. compute='_compute_groups',
  67. readonly=True,
  68. store=True,
  69. automatic=True,
  70. compute_sudo=self._access_groups_sudo,
  71. groups=self._access_groups_fields))
  72. #----------------------------------------------------------
  73. # Helper
  74. #----------------------------------------------------------
  75. @api.multi
  76. def _filter_access(self, operation):
  77. records = super(AccessGroupsModel, self)._filter_access(operation)
  78. return records.filter_access_groups(operation)
  79. @api.model
  80. def _apply_access_groups(self, query, mode='read'):
  81. if self.env.user.id == SUPERUSER_ID or isinstance(self.env.uid, NoSecurityUid):
  82. return None
  83. where_clause = '''
  84. "{table}".id IN (
  85. SELECT r.aid
  86. FROM {table}_complete_groups_rel r
  87. JOIN muk_security_access_groups g ON r.gid = g.id
  88. JOIN muk_security_access_groups_users_rel u ON r.gid = u.gid
  89. WHERE u.uid = %s AND g.perm_{mode} = true
  90. )
  91. '''.format(table=self._table, mode=mode)
  92. if not self._access_groups_strict:
  93. exists_clause = '''
  94. NOT EXISTS (
  95. SELECT 1
  96. FROM {table}_complete_groups_rel r
  97. JOIN muk_security_access_groups g ON r.gid = g.id
  98. WHERE r.aid = "{table}".id {groups_mode}
  99. )
  100. '''
  101. groups_mode = self._access_groups_mode and 'AND g.perm_{mode} = true'.format(mode=mode)
  102. exists_clause = exists_clause.format(table=self._table, groups_mode=groups_mode or "")
  103. where_clause = '({groups_clause} OR {exists_clause})'.format(
  104. groups_clause=where_clause,
  105. exists_clause=exists_clause,
  106. )
  107. query.where_clause += [where_clause]
  108. query.where_clause_params += [self.env.user.id]
  109. @api.model
  110. def _apply_ir_rules(self, query, mode='read'):
  111. super(AccessGroupsModel, self)._apply_ir_rules(query, mode=mode)
  112. self._apply_access_groups(query, mode=mode)
  113. @api.multi
  114. def _get_ids_without_access_groups(self, operation):
  115. sql_query = '''
  116. SELECT id
  117. FROM {table} a
  118. WHERE NOT EXISTS (
  119. SELECT 1
  120. FROM {table}_complete_groups_rel r
  121. JOIN muk_security_access_groups g ON r.gid = g.id
  122. WHERE r.aid = a.id {subset} {groups_mode}
  123. );
  124. '''
  125. subset = self.ids and 'AND r.aid = ANY (VALUES {ids})'.format(
  126. ids=', '.join(map(lambda id: '(%s)' % id, self.ids))
  127. )
  128. groups_mode = self._access_groups_mode and 'AND g.perm_{operation} = true'.format(
  129. operation=operation
  130. )
  131. sql_query = sql_query.format(
  132. table=self._table,
  133. subset=subset or "",
  134. groups_mode=groups_mode or "",
  135. )
  136. self.env.cr.execute(sql_query)
  137. return list(map(lambda val: val[0], self.env.cr.fetchall()))
  138. #----------------------------------------------------------
  139. # Function
  140. #----------------------------------------------------------
  141. @api.multi
  142. def check_access(self, operation, raise_exception=False):
  143. res = super(AccessGroupsModel, self).check_access(operation, raise_exception)
  144. try:
  145. return res and self.check_access_groups(operation) == None
  146. except AccessError:
  147. if raise_exception:
  148. raise
  149. return False
  150. #----------------------------------------------------------
  151. # Security
  152. #----------------------------------------------------------
  153. @api.multi
  154. def check_access_groups(self, operation):
  155. if self.env.user.id == SUPERUSER_ID or isinstance(self.env.uid, NoSecurityUid):
  156. return None
  157. group_ids = set(self.ids) - set(self._get_ids_without_access_groups(operation))
  158. if group_ids:
  159. sql_query = '''
  160. SELECT r.aid, perm_{operation}
  161. FROM {table}_complete_groups_rel r
  162. JOIN muk_security_access_groups g ON r.gid = g.id
  163. JOIN muk_security_access_groups_users_rel u ON r.gid = u.gid
  164. WHERE r.aid = ANY (VALUES {ids}) AND u.uid = %s;
  165. '''.format(
  166. operation=operation,
  167. table=self._table,
  168. ids=', '.join(map(lambda id: '(%s)' % id, group_ids)),
  169. )
  170. self.env.cr.execute(sql_query, [self.env.user.id])
  171. result = defaultdict(list)
  172. for key, val in self.env.cr.fetchall():
  173. result[key].append(val)
  174. if len(result.keys()) < len(group_ids) or not all(list(map(lambda val: any(val), result.values()))):
  175. raise AccessError(_(
  176. 'The requested operation cannot be completed due to group security restrictions. '
  177. 'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)'
  178. ) % (self._description, operation))
  179. @api.multi
  180. def filter_access_groups(self, operation):
  181. if self.env.user.id == SUPERUSER_ID or isinstance(self.env.uid, NoSecurityUid):
  182. return self
  183. ids_with_access = self._get_ids_without_access_groups(operation)
  184. group_ids = set(self.ids) - set(ids_with_access)
  185. if group_ids:
  186. sql_query = '''
  187. SELECT r.aid
  188. FROM {table}_complete_groups_rel r
  189. JOIN muk_security_access_groups g ON r.gid = g.id
  190. JOIN muk_security_access_groups_users_rel u ON r.gid = u.gid
  191. WHERE r.aid = ANY (VALUES {ids}) AND u.uid = %s AND g.perm_{operation} = true;
  192. '''.format(
  193. table=self._table,
  194. ids=', '.join(map(lambda id: '(%s)' % id, group_ids)),
  195. operation=operation,
  196. )
  197. self.env.cr.execute(sql_query, [self.env.user.id])
  198. ids_with_access += list(map(lambda val: val[0], self.env.cr.fetchall()))
  199. return self & self.browse(ids_with_access)
  200. #----------------------------------------------------------
  201. # Create, Update, Delete
  202. #----------------------------------------------------------
  203. @api.multi
  204. def _write(self, vals):
  205. self.check_access_groups('write')
  206. return super(AccessGroupsModel, self)._write(vals)
  207. @api.multi
  208. def unlink(self):
  209. self.check_access_groups('unlink')
  210. return super(AccessGroupsModel, self).unlink()
  211. #----------------------------------------------------------
  212. # Groups
  213. #----------------------------------------------------------
  214. @api.depends('groups')
  215. def _compute_groups(self):
  216. for record in self:
  217. record.complete_groups = record.groups