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.

236 lines
9.4 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. # Set it to True to enforced security even if no group has been set
  33. _access_groups_strict = False
  34. # Set it to True to let the non strict mode check for existing groups per mode
  35. _access_groups_mode = False
  36. #----------------------------------------------------------
  37. # Datebase
  38. #----------------------------------------------------------
  39. @api.model
  40. def _add_magic_fields(self):
  41. super(AccessGroupsModel, self)._add_magic_fields()
  42. def add(name, field):
  43. if name not in self._fields:
  44. self._add_field(name, field)
  45. add('groups', fields.Many2many(
  46. _module=self._module,
  47. comodel_name='muk_security.access_groups',
  48. relation='%s_groups_rel' % (self._table),
  49. column1='aid',
  50. column2='gid',
  51. string="Groups",
  52. automatic=True,
  53. groups=self._access_groups_fields))
  54. add('complete_groups', fields.Many2many(
  55. _module=self._module,
  56. comodel_name='muk_security.access_groups',
  57. relation='%s_complete_groups_rel' % (self._table),
  58. column1='aid',
  59. column2='gid',
  60. string="Complete Groups",
  61. compute='_compute_groups',
  62. store=True,
  63. automatic=True,
  64. groups=self._access_groups_fields))
  65. #----------------------------------------------------------
  66. # Helper
  67. #----------------------------------------------------------
  68. @api.multi
  69. def _filter_access(self, operation):
  70. records = super(AccessGroupsModel, self)._filter_access(operation)
  71. return records.filter_access_groups(operation)
  72. @api.model
  73. def _apply_access_groups(self, query, mode='read'):
  74. if self.env.user.id == SUPERUSER_ID or isinstance(self.env.uid, NoSecurityUid):
  75. return None
  76. where_clause = '''
  77. "{table}".id IN (
  78. SELECT r.aid
  79. FROM {table}_complete_groups_rel r
  80. JOIN muk_security_access_groups g ON r.gid = g.id
  81. JOIN muk_security_access_groups_users_rel u ON r.gid = u.gid
  82. WHERE u.uid = %s AND g.perm_{mode} = true
  83. )
  84. '''.format(table=self._table, mode=mode)
  85. if not self._access_groups_strict:
  86. exists_clause = '''
  87. NOT EXISTS (
  88. SELECT 1
  89. FROM {table}_complete_groups_rel r
  90. JOIN muk_security_access_groups g ON r.gid = g.id
  91. WHERE r.aid = "{table}".id {groups_mode}
  92. )
  93. '''
  94. groups_mode = self._access_groups_mode and 'AND g.perm_{mode} = true'.format(mode=mode)
  95. exists_clause = exists_clause.format(table=self._table, groups_mode=groups_mode or "")
  96. where_clause = '({groups_clause} OR {exists_clause})'.format(
  97. groups_clause=where_clause,
  98. exists_clause=exists_clause,
  99. )
  100. query.where_clause += [where_clause]
  101. query.where_clause_params += [self.env.user.id]
  102. @api.model
  103. def _apply_ir_rules(self, query, mode='read'):
  104. super(AccessGroupsModel, self)._apply_ir_rules(query, mode=mode)
  105. self._apply_access_groups(query, mode=mode)
  106. @api.multi
  107. def _get_ids_without_access_groups(self, operation):
  108. sql_query = '''
  109. SELECT id
  110. FROM {table} a
  111. WHERE NOT EXISTS (
  112. SELECT 1
  113. FROM {table}_complete_groups_rel r
  114. JOIN muk_security_access_groups g ON r.gid = g.id
  115. WHERE r.aid = a.id {subset} {groups_mode}
  116. );
  117. '''
  118. subset = self.ids and 'AND r.aid = ANY (VALUES {ids})'.format(
  119. ids=', '.join(map(lambda id: '(%s)' % id, self.ids))
  120. )
  121. groups_mode = self._access_groups_mode and 'AND g.perm_{operation} = true'.format(
  122. operation=operation
  123. )
  124. sql_query = sql_query.format(
  125. table=self._table,
  126. subset=subset or "",
  127. groups_mode=groups_mode or "",
  128. )
  129. self.env.cr.execute(sql_query)
  130. return list(map(lambda val: val[0], self.env.cr.fetchall()))
  131. #----------------------------------------------------------
  132. # Function
  133. #----------------------------------------------------------
  134. @api.multi
  135. def check_access(self, operation, raise_exception=False):
  136. res = super(AccessGroupsModel, self).check_access(operation, raise_exception)
  137. try:
  138. return res and self.check_access_groups(operation) == None
  139. except AccessError:
  140. if raise_exception:
  141. raise
  142. return False
  143. #----------------------------------------------------------
  144. # Security
  145. #----------------------------------------------------------
  146. @api.multi
  147. def check_access_groups(self, operation):
  148. if self.env.user.id == SUPERUSER_ID or isinstance(self.env.uid, NoSecurityUid):
  149. return None
  150. group_ids = set(self.ids) - set(self._get_ids_without_access_groups(operation))
  151. if group_ids:
  152. sql_query = '''
  153. SELECT r.aid, perm_{operation}
  154. FROM {table}_complete_groups_rel r
  155. JOIN muk_security_access_groups g ON r.gid = g.id
  156. JOIN muk_security_access_groups_users_rel u ON r.gid = u.gid
  157. WHERE r.aid = ANY (VALUES {ids}) AND u.uid = %s;
  158. '''.format(
  159. operation=operation,
  160. table=self._table,
  161. ids=', '.join(map(lambda id: '(%s)' % id, group_ids)),
  162. )
  163. self.env.cr.execute(sql_query, [self.env.user.id])
  164. result = defaultdict(list)
  165. for key, val in self.env.cr.fetchall():
  166. result[key].append(val)
  167. if len(result.keys()) < len(group_ids) or not all(list(map(lambda val: any(val), result.values()))):
  168. raise AccessError(_(
  169. 'The requested operation cannot be completed due to group security restrictions. '
  170. 'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)'
  171. ) % (self._description, operation))
  172. @api.multi
  173. def filter_access_groups(self, operation):
  174. if self.env.user.id == SUPERUSER_ID or isinstance(self.env.uid, NoSecurityUid):
  175. return self
  176. ids_with_access = self._get_ids_without_access_groups(operation)
  177. group_ids = set(self.ids) - set(ids_with_access)
  178. if group_ids:
  179. sql_query = '''
  180. SELECT r.aid
  181. FROM {table}_complete_groups_rel r
  182. JOIN muk_security_access_groups g ON r.gid = g.id
  183. JOIN muk_security_access_groups_users_rel u ON r.gid = u.gid
  184. WHERE r.aid = ANY (VALUES {ids}) AND u.uid = %s AND g.perm_{operation} = true;
  185. '''.format(
  186. table=self._table,
  187. ids=', '.join(map(lambda id: '(%s)' % id, group_ids)),
  188. operation=operation,
  189. )
  190. self.env.cr.execute(sql_query, [self.env.user.id])
  191. ids_with_access += list(map(lambda val: val[0], self.env.cr.fetchall()))
  192. return self.browse(ids_with_access)
  193. #----------------------------------------------------------
  194. # Create, Update, Delete
  195. #----------------------------------------------------------
  196. @api.multi
  197. def _write(self, vals):
  198. self.check_access_groups('write')
  199. return super(AccessGroupsModel, self)._write(vals)
  200. @api.multi
  201. def unlink(self):
  202. self.check_access_groups('unlink')
  203. return super(AccessGroupsModel, self).unlink()
  204. #----------------------------------------------------------
  205. # Groups
  206. #----------------------------------------------------------
  207. @api.depends('groups')
  208. def _compute_groups(self):
  209. for record in self:
  210. record.complete_groups = record.groups