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.

241 lines
9.6 KiB

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