has_groups.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. module HasGroups
  3. extend ActiveSupport::Concern
  4. included do
  5. before_destroy :destroy_group_relations
  6. attr_accessor :group_access_buffer
  7. after_create :check_group_access_buffer
  8. after_update :check_group_access_buffer
  9. association_attributes_ignored :groups
  10. has_many group_through_identifier
  11. has_many :groups, through: group_through_identifier do
  12. # A helper to join the :through table into the result of groups to access :through attributes
  13. #
  14. # @param [String, Array<String>] access Limiting to one or more access verbs. 'full' gets added automatically
  15. #
  16. # @example All access groups
  17. # user.groups.access
  18. # #=> [#<Group id: 1, access="read", ...>, ...]
  19. #
  20. # @example Groups for given access(es) plus 'full'
  21. # user.groups.access('read')
  22. # #=> [#<Group id: 1, access="full", ...>, ...]
  23. #
  24. # @example Groups for given access(es)es plus 'full'
  25. # user.groups.access('read', 'write')
  26. # #=> [#<Group id: 1, access="full", ...>, ...]
  27. #
  28. # @return [ActiveRecord::AssociationRelation<[<Group]>] List of Groups with :through attributes
  29. def access(*access)
  30. table_name = proxy_association.owner.class.group_through.table_name
  31. query = select("groups.*, #{table_name}.*")
  32. return query if access.blank?
  33. access.push('full') if !access.include?('full')
  34. query.where("#{table_name}.access" => access)
  35. end
  36. end
  37. end
  38. # Checks a given Group( ID) for given access(es) for the instance.
  39. # Checks indirect access via Roles if instance has Roles, too.
  40. #
  41. # @example Group ID param
  42. # user.group_access?(1, 'read')
  43. # #=> true
  44. #
  45. # @example Group param
  46. # user.group_access?(group, 'read')
  47. # #=> true
  48. #
  49. # @example Access list
  50. # user.group_access?(group, ['read', 'create'])
  51. # #=> true
  52. #
  53. # @return [Boolean]
  54. def group_access?(group_id, access)
  55. return false if !active?
  56. return false if !groups_access_permission?
  57. group_id = self.class.ensure_group_id_parameter(group_id)
  58. access = self.class.ensure_group_access_list_parameter(access)
  59. # check direct access
  60. return true if group_through.klass.includes(:group).exists?(
  61. group_through.foreign_key => id,
  62. group_id: group_id,
  63. access: access,
  64. groups: {
  65. active: true
  66. }
  67. )
  68. # check indirect access through Roles if possible
  69. return false if !respond_to?(:role_access?)
  70. role_access?(group_id, access)
  71. end
  72. # Lists the Group IDs the instance has the given access(es) plus 'full' to.
  73. # Adds indirect accessable Group IDs via Roles if instance has Roles, too.
  74. #
  75. # @example Single access
  76. # user.group_ids_access('read')
  77. # #=> [1, 3, ...]
  78. #
  79. # @example Access list
  80. # user.group_ids_access(['read', 'create'])
  81. # #=> [1, 3, ...]
  82. #
  83. # @return [Array<Integer>] Group IDs the instance has the given access(es) to.
  84. def group_ids_access(access)
  85. return [] if !active?
  86. return [] if !groups_access_permission?
  87. access = self.class.ensure_group_access_list_parameter(access)
  88. foreign_key = group_through.foreign_key
  89. klass = group_through.klass
  90. # check direct access
  91. ids = klass.includes(:group).where(foreign_key => id, access: access, groups: { active: true }).pluck(:group_id)
  92. ids ||= []
  93. # check indirect access through roles if possible
  94. return ids if !respond_to?(:role_ids)
  95. role_group_ids = RoleGroup.includes(:group).where(role_id: role_ids, access: access, groups: { active: true }).pluck(:group_id)
  96. # combines and removes duplicates
  97. # and returns them in one statement
  98. ids | role_group_ids
  99. end
  100. # Lists Groups the instance has the given access(es) plus 'full' to.
  101. # Adds indirect accessable Groups via Roles if instance has Roles, too.
  102. #
  103. # @example Single access
  104. # user.groups_access('read')
  105. # #=> [#<Group id: 1, access="read", ...>, ...]
  106. #
  107. # @example Access list
  108. # user.groups_access(['read', 'create'])
  109. # #=> [#<Group id: 1, access="read", ...>, ...]
  110. #
  111. # @return [Array<Group>] Groups the instance has the given access(es) to.
  112. def groups_access(access)
  113. return [] if !active?
  114. return [] if !groups_access_permission?
  115. group_ids = group_ids_access(access)
  116. Group.where(id: group_ids)
  117. end
  118. # Returns a map of Group name to access
  119. #
  120. # @example
  121. # user.group_names_access_map
  122. # #=> {'Users' => 'full', 'Support' => ['read', 'write']}
  123. #
  124. # @return [Hash<String=>String,Array<String>>] The map of Group name to access
  125. def group_names_access_map
  126. groups_access_map(:name)
  127. end
  128. # Stores a map of Group ID to access. Deletes all other relations.
  129. #
  130. # @example
  131. # user.group_names_access_map = {'Users' => 'full', 'Support' => ['read', 'write']}
  132. # #=> {'Users' => 'full', 'Support' => ['read', 'write']}
  133. #
  134. # @return [Hash<String=>String,Array<String>>] The given map
  135. def group_names_access_map=(name_access_map)
  136. groups_access_map_store(name_access_map) do |group_name|
  137. Group.where(name: group_name).pluck(:id).first
  138. end
  139. end
  140. # Returns a map of Group ID to access
  141. #
  142. # @example
  143. # user.group_ids_access_map
  144. # #=> {1 => 'full', 42 => ['read', 'write']}
  145. #
  146. # @return [Hash<Integer=>String,Array<String>>] The map of Group ID to access
  147. def group_ids_access_map
  148. groups_access_map(:id)
  149. end
  150. # Stores a map of Group ID to access. Deletes all other relations.
  151. #
  152. # @example
  153. # user.group_ids_access_map = {1 => 'full', 42 => ['read', 'write']}
  154. # #=> {1 => 'full', 42 => ['read', 'write']}
  155. #
  156. # @return [Hash<Integer=>String,Array<String>>] The given map
  157. def group_ids_access_map=(id_access_map)
  158. groups_access_map_store(id_access_map)
  159. end
  160. # An alias to .groups class method
  161. def group_through
  162. @group_through ||= self.class.group_through
  163. end
  164. # Checks if the instance has general permission to Group access.
  165. #
  166. # @example
  167. # customer_user.groups_access_permission?
  168. # #=> false
  169. #
  170. # @return [Boolean]
  171. def groups_access_permission?
  172. return true if !respond_to?(:permissions?)
  173. permissions?('ticket.agent')
  174. end
  175. private
  176. def groups_access_map(key)
  177. return {} if !active?
  178. return {} if !groups_access_permission?
  179. {}.tap do |hash|
  180. groups.access.where(active: true).pluck(key, :access).each do |entry|
  181. hash[ entry[0] ] ||= []
  182. hash[ entry[0] ].push(entry[1])
  183. end
  184. end
  185. end
  186. def groups_access_map_store(map)
  187. map.each do |group_identifier, accesses|
  188. # use given key as identifier or look it up
  189. # via the given block which returns the identifier
  190. group_id = block_given? ? yield(group_identifier) : group_identifier
  191. if !accesses.is_a?(Array)
  192. accesses = [accesses]
  193. end
  194. accesses.each do |access|
  195. push_group_access_buffer(
  196. group_id: group_id,
  197. access: access
  198. )
  199. end
  200. end
  201. check_group_access_buffer if id
  202. end
  203. def push_group_access_buffer(entry)
  204. @group_access_buffer ||= []
  205. @group_access_buffer.push(entry)
  206. end
  207. def check_group_access_buffer
  208. return if group_access_buffer.blank?
  209. destroy_group_relations
  210. foreign_key = group_through.foreign_key
  211. entries = group_access_buffer.collect do |entry|
  212. entry[foreign_key] = id
  213. entry
  214. end
  215. group_through.klass.create!(entries)
  216. group_access_buffer = nil
  217. cache_delete
  218. true
  219. end
  220. def destroy_group_relations
  221. group_through.klass.destroy_all(group_through.foreign_key => id)
  222. end
  223. # methods defined here are going to extend the class, not the instance of it
  224. class_methods do
  225. # Lists IDs of instances having the given access(es) to the given Group.
  226. #
  227. # @example Group ID param
  228. # User.group_access_ids(1, 'read')
  229. # #=> [1, 3, ...]
  230. #
  231. # @example Group param
  232. # User.group_access_ids(group, 'read')
  233. # #=> [1, 3, ...]
  234. #
  235. # @example Access list
  236. # User.group_access_ids(group, ['read', 'create'])
  237. # #=> [1, 3, ...]
  238. #
  239. # @return [Array<Integer>]
  240. def group_access_ids(group_id, access)
  241. group_access(group_id, access).collect(&:id)
  242. end
  243. # Lists instances having the given access(es) to the given Group.
  244. #
  245. # @example Group ID param
  246. # User.group_access(1, 'read')
  247. # #=> [#<User id: 1, ...>, ...]
  248. #
  249. # @example Group param
  250. # User.group_access(group, 'read')
  251. # #=> [#<User id: 1, ...>, ...]
  252. #
  253. # @example Access list
  254. # User.group_access(group, ['read', 'create'])
  255. # #=> [#<User id: 1, ...>, ...]
  256. #
  257. # @return [Array<Class>]
  258. def group_access(group_id, access)
  259. group_id = ensure_group_id_parameter(group_id)
  260. access = ensure_group_access_list_parameter(access)
  261. # check direct access
  262. ids = group_through.klass.includes(name.downcase).where(group_id: group_id, access: access, table_name => { active: true }).pluck(group_through.foreign_key)
  263. ids ||= []
  264. # get instances and check for required permission
  265. instances = where(id: ids).select(&:groups_access_permission?)
  266. # check indirect access through roles if possible
  267. return instances if !respond_to?(:role_access)
  268. # combines and removes duplicates
  269. # and returns them in one statement
  270. instances | role_access(group_id, access)
  271. end
  272. # The reflection instance containing the association data
  273. #
  274. # @example
  275. # User.group_through
  276. # #=> <ActiveRecord::Reflection::HasManyReflection:0x007fd2f5785440 @name=:user_groups, ...>
  277. #
  278. # @return [ActiveRecord::Reflection::HasManyReflection] The given map
  279. def group_through
  280. @group_through ||= reflect_on_association(group_through_identifier)
  281. end
  282. # The identifier of the has_many :through relation
  283. #
  284. # @example
  285. # User.group_through_identifier
  286. # #=> :user_groups
  287. #
  288. # @return [Symbol] The relation identifier
  289. def group_through_identifier
  290. "#{name.downcase}_groups".to_sym
  291. end
  292. def ensure_group_id_parameter(group_or_id)
  293. return group_or_id if group_or_id.is_a?(Integer)
  294. group_or_id.id
  295. end
  296. def ensure_group_access_list_parameter(access)
  297. access = [access] if access.is_a?(String)
  298. access.push('full') if !access.include?('full')
  299. access
  300. end
  301. end
  302. end