Browse Source

Added general check for Group access based on agent.ticket permission.

Thorsten Eckel 7 years ago
parent
commit
18a9eed038

+ 36 - 18
app/models/concerns/has_groups.rb

@@ -7,8 +7,8 @@ module HasGroups
 
     attr_accessor :group_access_buffer
 
-    after_create  :check_group_access_buffer
-    after_update  :check_group_access_buffer
+    after_create :check_group_access_buffer
+    after_update :check_group_access_buffer
 
     association_attributes_ignored :groups
 
@@ -62,6 +62,7 @@ module HasGroups
   # @return [Boolean]
   def group_access?(group_id, access)
     return false if !active?
+    return false if !groups_access_permission?
 
     group_id = self.class.ensure_group_id_parameter(group_id)
     access   = self.class.ensure_group_access_list_parameter(access)
@@ -95,6 +96,7 @@ module HasGroups
   # @return [Array<Integer>] Group IDs the instance has the given access(es) to.
   def group_ids_access(access)
     return [] if !active?
+    return [] if !groups_access_permission?
 
     access      = self.class.ensure_group_access_list_parameter(access)
     foreign_key = group_through.foreign_key
@@ -128,6 +130,7 @@ module HasGroups
   # @return [Array<Group>] Groups the instance has the given access(es) to.
   def groups_access(access)
     return [] if !active?
+    return [] if !groups_access_permission?
     group_ids = group_ids_access(access)
     Group.where(id: group_ids)
   end
@@ -183,10 +186,24 @@ module HasGroups
     @group_through ||= self.class.group_through
   end
 
+  # Checks if the instance has general permission to Group access.
+  #
+  # @example
+  #   customer_user.groups_access_permission?
+  #   #=> false
+  #
+  # @return [Boolean]
+  def groups_access_permission?
+    return true if !respond_to?(:permissions?)
+    permissions?('ticket.agent')
+  end
+
   private
 
   def groups_access_map(key)
     return {} if !active?
+    return {} if !groups_access_permission?
+
     {}.tap do |hash|
       groups.access.where(active: true).pluck(key, :access).each do |entry|
         hash[ entry[0] ] ||= []
@@ -262,20 +279,7 @@ module HasGroups
     #
     # @return [Array<Integer>]
     def group_access_ids(group_id, access)
-      group_id = ensure_group_id_parameter(group_id)
-      access   = ensure_group_access_list_parameter(access)
-
-      # check direct access
-      ids   = group_through.klass.includes(name.downcase).where(group_id: group_id, access: access, table_name => { active: true }).pluck(group_through.foreign_key)
-      ids ||= []
-
-      # check indirect access through roles if possible
-      return ids if !respond_to?(:role_access_ids)
-      role_instance_ids = role_access_ids(group_id, access)
-
-      # combines and removes duplicates
-      # and returns them in one statement
-      ids | role_instance_ids
+      group_access(group_id, access).collect(&:id)
     end
 
     # Lists instances having the given access(es) to the given Group.
@@ -294,8 +298,22 @@ module HasGroups
     #
     # @return [Array<Class>]
     def group_access(group_id, access)
-      instance_ids = group_access_ids(group_id, access)
-      where(id: instance_ids)
+      group_id = ensure_group_id_parameter(group_id)
+      access   = ensure_group_access_list_parameter(access)
+
+      # check direct access
+      ids   = group_through.klass.includes(name.downcase).where(group_id: group_id, access: access, table_name => { active: true }).pluck(group_through.foreign_key)
+      ids ||= []
+
+      # get instances and check for required permission
+      instances = where(id: ids).select(&:groups_access_permission?)
+
+      # check indirect access through roles if possible
+      return instances if !respond_to?(:role_access)
+
+      # combines and removes duplicates
+      # and returns them in one statement
+      instances | role_access(group_id, access)
     end
 
     # The reflection instance containing the association data

+ 27 - 6
app/models/concerns/has_roles.rb

@@ -18,6 +18,8 @@ module HasRoles
   #
   # @return [Boolean]
   def role_access?(group_id, access)
+    return false if !groups_access_permission?
+
     group_id = self.class.ensure_group_id_parameter(group_id)
     access   = self.class.ensure_group_access_list_parameter(access)
 
@@ -37,28 +39,47 @@ module HasRoles
   # methods defined here are going to extend the class, not the instance of it
   class_methods do
 
-    # Lists IDs of instances having the given access(es) to the given Group through Roles.
+    # Lists instances having the given access(es) to the given Group through Roles.
     #
     # @example Group ID param
-    #   User.role_access_ids(1, 'read')
+    #   User.role_access(1, 'read')
     #   #=> [1, 3, ...]
     #
     # @example Group param
-    #   User.role_access_ids(group, 'read')
+    #   User.role_access(group, 'read')
     #   #=> [1, 3, ...]
     #
     # @example Access list
-    #   User.role_access_ids(group, ['read', 'create'])
+    #   User.role_access(group, ['read', 'create'])
     #   #=> [1, 3, ...]
     #
     # @return [Array<Integer>]
-    def role_access_ids(group_id, access)
+    def role_access(group_id, access)
       group_id = ensure_group_id_parameter(group_id)
       access   = ensure_group_access_list_parameter(access)
 
       role_ids   = RoleGroup.includes(:role).where(group_id: group_id, access: access, roles: { active: true }).pluck(:role_id)
       join_table = reflect_on_association(:roles).join_table
-      includes(:roles).where(active: true, join_table => { role_id: role_ids }).distinct.pluck(:id)
+      joins(:roles).where(active: true, join_table => { role_id: role_ids }).distinct.select(&:groups_access_permission?)
+    end
+
+    # Lists IDs of instances having the given access(es) to the given Group through Roles.
+    #
+    # @example Group ID param
+    #   User.role_access_ids(1, 'read')
+    #   #=> [1, 3, ...]
+    #
+    # @example Group param
+    #   User.role_access_ids(group, 'read')
+    #   #=> [1, 3, ...]
+    #
+    # @example Access list
+    #   User.role_access_ids(group, ['read', 'create'])
+    #   #=> [1, 3, ...]
+    #
+    # @return [Array<Integer>]
+    def role_access_ids(group_id, access)
+      role_access(group_id, access).collect(&:id)
     end
 
     def ensure_group_id_parameter(group_or_id)

+ 1 - 1
app/models/ticket.rb

@@ -768,7 +768,7 @@ perform changes on ticket
             email = User.lookup(id: owner_id).email
             recipients_raw.push(email)
           elsif recipient == 'ticket_agents'
-            User.group_access(group_id, 'full').order(:login).each do |user|
+            User.group_access(group_id, 'full').sort_by(&:login).each do |user|
               recipients_raw.push(user.email)
             end
           else

+ 2 - 1
app/models/transaction/notification.rb

@@ -47,10 +47,11 @@ class Transaction::Notification
     recipients_and_channels = []
 
     # loop through all users
-    possible_recipients = User.group_access(ticket.group_id, 'full').order(:login)
+    possible_recipients = User.group_access(ticket.group_id, 'full').sort_by(&:login)
     if ticket.owner_id == 1
       possible_recipients.push ticket.owner
     end
+
     already_checked_recipient_ids = {}
     possible_recipients.each { |user|
       result = NotificationFactory::Mailer.notification_settings(user, ticket, @item[:type])

+ 150 - 150
spec/models/concerns/has_groups_examples.rb

@@ -1,10 +1,12 @@
+# Requires: let(:group_access_instance) { ... }
+# Requires: let(:new_group_access_instance) { ... }
 RSpec.shared_examples 'HasGroups' do
 
   context 'group' do
-
-    let(:factory_name) { described_class.name.downcase.to_sym }
-    let(:instance) { create(factory_name) }
-    let(:instance_inactive) { create(factory_name, active: false) }
+    let(:group_access_instance_inactive) {
+      group_access_instance.update_attribute(:active, false)
+      group_access_instance
+    }
     let(:group_full) { create(:group) }
     let(:group_read) { create(:group) }
     let(:group_inactive) { create(:group, active: false) }
@@ -20,7 +22,7 @@ RSpec.shared_examples 'HasGroups' do
       end
 
       it 'instance responds to group_through_identifier method' do
-        expect(instance).to respond_to(described_class.group_through_identifier)
+        expect(group_access_instance).to respond_to(described_class.group_through_identifier)
       end
     end
 
@@ -38,19 +40,19 @@ RSpec.shared_examples 'HasGroups' do
     context '#groups' do
 
       it 'responds to groups' do
-        expect(instance).to respond_to(:groups)
+        expect(group_access_instance).to respond_to(:groups)
       end
 
       context '#groups.access' do
 
         it 'responds to groups.access' do
-          expect(instance.groups).to respond_to(:access)
+          expect(group_access_instance.groups).to respond_to(:access)
         end
 
         context 'result' do
 
           before(:each) do
-            instance.group_names_access_map = {
+            group_access_instance.group_names_access_map = {
               group_full.name     => 'full',
               group_read.name     => 'read',
               group_inactive.name => 'write',
@@ -58,23 +60,23 @@ RSpec.shared_examples 'HasGroups' do
           end
 
           it 'returns all related Groups' do
-            expect(instance.groups.access.size).to eq(3)
+            expect(group_access_instance.groups.access.size).to eq(3)
           end
 
           it 'adds join table attribute(s like) access' do
-            expect(instance.groups.access.first).to respond_to(:access)
+            expect(group_access_instance.groups.access.first).to respond_to(:access)
           end
 
           it 'filters for given access parameter' do
-            expect(instance.groups.access('read')).to include(group_read)
+            expect(group_access_instance.groups.access('read')).to include(group_read)
           end
 
           it 'filters for given access list parameter' do
-            expect(instance.groups.access('read', 'write')).to include(group_read, group_inactive)
+            expect(group_access_instance.groups.access('read', 'write')).to include(group_read, group_inactive)
           end
 
           it 'always includes full access groups' do
-            expect(instance.groups.access('read')).to include(group_full)
+            expect(group_access_instance.groups.access('read')).to include(group_full)
           end
         end
       end
@@ -82,16 +84,16 @@ RSpec.shared_examples 'HasGroups' do
 
     context '#group_access?' do
 
+      it 'responds to group_access?' do
+        expect(group_access_instance).to respond_to(:group_access?)
+      end
+
       before(:each) do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_read.name => 'read',
         }
       end
 
-      it 'responds to group_access?' do
-        expect(instance).to respond_to(:group_access?)
-      end
-
       context 'Group ID parameter' do
         include_examples '#group_access? call' do
           let(:group_parameter) { group_read.id }
@@ -105,61 +107,61 @@ RSpec.shared_examples 'HasGroups' do
       end
 
       it 'prevents inactive Group' do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_inactive.name => 'read',
         }
 
-        expect(instance.group_access?(group_inactive.id, 'read')).to be false
+        expect(group_access_instance.group_access?(group_inactive.id, 'read')).to be false
       end
 
       it 'prevents inactive instances' do
-        instance_inactive.group_names_access_map = {
+        group_access_instance_inactive.group_names_access_map = {
           group_read.name => 'read',
         }
 
-        expect(instance_inactive.group_access?(group_read.id, 'read')).to be false
+        expect(group_access_instance_inactive.group_access?(group_read.id, 'read')).to be false
       end
     end
 
     context '#group_ids_access' do
 
+      it 'responds to group_ids_access' do
+        expect(group_access_instance).to respond_to(:group_ids_access)
+      end
+
       before(:each) do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_read.name => 'read',
         }
       end
 
-      it 'responds to group_ids_access' do
-        expect(instance).to respond_to(:group_ids_access)
-      end
-
       it 'lists only active Group IDs' do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_read.name     => 'read',
           group_inactive.name => 'read',
         }
 
-        result = instance.group_ids_access('read')
+        result = group_access_instance.group_ids_access('read')
         expect(result).not_to include(group_inactive.id)
       end
 
       it "doesn't list for inactive instances" do
-        instance_inactive.group_names_access_map = {
+        group_access_instance_inactive.group_names_access_map = {
           group_read.name => 'read',
         }
 
-        expect(instance_inactive.group_ids_access('read')).to be_empty
+        expect(group_access_instance_inactive.group_ids_access('read')).to be_empty
       end
 
       context 'single access' do
 
         it 'lists access Group IDs' do
-          result = instance.group_ids_access('read')
+          result = group_access_instance.group_ids_access('read')
           expect(result).to include(group_read.id)
         end
 
         it "doesn't list for no access" do
-          result = instance.group_ids_access('write')
+          result = group_access_instance.group_ids_access('write')
           expect(result).not_to include(group_read.id)
         end
       end
@@ -167,12 +169,12 @@ RSpec.shared_examples 'HasGroups' do
       context 'access list' do
 
         it 'lists access Group IDs' do
-          result = instance.group_ids_access(%w(read write))
+          result = group_access_instance.group_ids_access(%w(read write))
           expect(result).to include(group_read.id)
         end
 
         it "doesn't list for no access" do
-          result = instance.group_ids_access(%w(write create))
+          result = group_access_instance.group_ids_access(%w(write create))
           expect(result).not_to include(group_read.id)
         end
       end
@@ -181,19 +183,19 @@ RSpec.shared_examples 'HasGroups' do
     context '#groups_access' do
 
       it 'responds to groups_access' do
-        expect(instance).to respond_to(:groups_access)
+        expect(group_access_instance).to respond_to(:groups_access)
       end
 
       it 'wraps #group_ids_access' do
-        expect(instance).to receive(:group_ids_access)
-        instance.groups_access('read')
+        expect(group_access_instance).to receive(:group_ids_access)
+        group_access_instance.groups_access('read')
       end
 
       it 'returns Groups' do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_read.name => 'read',
         }
-        result = instance.groups_access('read')
+        result = group_access_instance.groups_access('read')
         expect(result).to include(group_read)
       end
     end
@@ -201,14 +203,14 @@ RSpec.shared_examples 'HasGroups' do
     context '#group_names_access_map=' do
 
       it 'responds to group_names_access_map=' do
-        expect(instance).to respond_to(:group_names_access_map=)
+        expect(group_access_instance).to respond_to(:group_names_access_map=)
       end
 
-      context 'Group name => access relation storage' do
+      context 'existing instance' do
 
         it 'stores Hash with String values' do
           expect do
-            instance.group_names_access_map = {
+            group_access_instance.group_names_access_map = {
               group_full.name => 'full',
               group_read.name => 'read',
             }
@@ -217,9 +219,9 @@ RSpec.shared_examples 'HasGroups' do
           }.by(2)
         end
 
-        it 'stores Hash with String values' do
+        it 'stores Hash with Array<String> values' do
           expect do
-            instance.group_names_access_map = {
+            group_access_instance.group_names_access_map = {
               group_full.name => 'full',
               group_read.name => %w(read write),
             }
@@ -227,33 +229,32 @@ RSpec.shared_examples 'HasGroups' do
             described_class.group_through.klass.count
           }.by(3)
         end
+      end
 
-        context 'new instance' do
-          let(:new_instance) { build(factory_name) }
-
-          it "doesn't store directly" do
-            expect do
-              new_instance.group_names_access_map = {
-                group_full.name => 'full',
-                group_read.name => 'read',
-              }
-            end.not_to change {
-              described_class.group_through.klass.count
+      context 'new instance' do
+
+        it "doesn't store directly" do
+          expect do
+            new_group_access_instance.group_names_access_map = {
+              group_full.name => 'full',
+              group_read.name => 'read',
             }
-          end
+          end.not_to change {
+            described_class.group_through.klass.count
+          }
+        end
 
-          it 'stores after save' do
-            expect do
-              new_instance.group_names_access_map = {
-                group_full.name => 'full',
-                group_read.name => 'read',
-              }
-
-              new_instance.save
-            end.to change {
-              described_class.group_through.klass.count
-            }.by(2)
-          end
+        it 'stores after save' do
+          expect do
+            new_group_access_instance.group_names_access_map = {
+              group_full.name => 'full',
+              group_read.name => 'read',
+            }
+
+            new_group_access_instance.save
+          end.to change {
+            described_class.group_through.klass.count
+          }.by(2)
         end
       end
     end
@@ -261,7 +262,7 @@ RSpec.shared_examples 'HasGroups' do
     context '#group_names_access_map' do
 
       it 'responds to group_names_access_map' do
-        expect(instance).to respond_to(:group_names_access_map)
+        expect(group_access_instance).to respond_to(:group_names_access_map)
       end
 
       it 'returns instance Group name => access relations as Hash' do
@@ -270,32 +271,32 @@ RSpec.shared_examples 'HasGroups' do
           group_read.name => ['read'],
         }
 
-        instance.group_names_access_map = expected
+        group_access_instance.group_names_access_map = expected
 
-        expect(instance.group_names_access_map).to eq(expected)
+        expect(group_access_instance.group_names_access_map).to eq(expected)
       end
 
       it "doesn't map for inactive instances" do
-        instance_inactive.group_names_access_map = {
+        group_access_instance_inactive.group_names_access_map = {
           group_full.name => ['full'],
           group_read.name => ['read'],
         }
 
-        expect(instance_inactive.group_names_access_map).to be_empty
+        expect(group_access_instance_inactive.group_names_access_map).to be_empty
       end
     end
 
     context '#group_ids_access_map=' do
 
       it 'responds to group_ids_access_map=' do
-        expect(instance).to respond_to(:group_ids_access_map=)
+        expect(group_access_instance).to respond_to(:group_ids_access_map=)
       end
 
-      context 'Group ID => access relation storage' do
+      context 'existing instance' do
 
         it 'stores Hash with String values' do
           expect do
-            instance.group_ids_access_map = {
+            group_access_instance.group_ids_access_map = {
               group_full.id => 'full',
               group_read.id => 'read',
             }
@@ -306,7 +307,7 @@ RSpec.shared_examples 'HasGroups' do
 
         it 'stores Hash with String values' do
           expect do
-            instance.group_ids_access_map = {
+            group_access_instance.group_ids_access_map = {
               group_full.id => 'full',
               group_read.id => %w(read write),
             }
@@ -314,33 +315,32 @@ RSpec.shared_examples 'HasGroups' do
             described_class.group_through.klass.count
           }.by(3)
         end
+      end
+
+      context 'new instance' do
+
+        it "doesn't store directly" do
+          expect do
+            new_group_access_instance.group_ids_access_map = {
+              group_full.id => 'full',
+              group_read.id => 'read',
+            }
+          end.not_to change {
+            described_class.group_through.klass.count
+          }
+        end
 
-        context 'new instance' do
-          let(:new_instance) { build(factory_name) }
-
-          it "doesn't store directly" do
-            expect do
-              new_instance.group_ids_access_map = {
-                group_full.id => 'full',
-                group_read.id => 'read',
-              }
-            end.not_to change {
-              described_class.group_through.klass.count
+        it 'stores after save' do
+          expect do
+            new_group_access_instance.group_ids_access_map = {
+              group_full.id => 'full',
+              group_read.id => 'read',
             }
-          end
 
-          it 'stores after save' do
-            expect do
-              new_instance.group_ids_access_map = {
-                group_full.id => 'full',
-                group_read.id => 'read',
-              }
-
-              new_instance.save
-            end.to change {
-              described_class.group_through.klass.count
-            }.by(2)
-          end
+            new_group_access_instance.save
+          end.to change {
+            described_class.group_through.klass.count
+          }.by(2)
         end
       end
     end
@@ -348,7 +348,7 @@ RSpec.shared_examples 'HasGroups' do
     context '#group_ids_access_map' do
 
       it 'responds to group_ids_access_map' do
-        expect(instance).to respond_to(:group_ids_access_map)
+        expect(group_access_instance).to respond_to(:group_ids_access_map)
       end
 
       it 'returns instance Group ID => access relations as Hash' do
@@ -357,18 +357,18 @@ RSpec.shared_examples 'HasGroups' do
           group_read.id => ['read'],
         }
 
-        instance.group_ids_access_map = expected
+        group_access_instance.group_ids_access_map = expected
 
-        expect(instance.group_ids_access_map).to eq(expected)
+        expect(group_access_instance.group_ids_access_map).to eq(expected)
       end
 
       it "doesn't map for inactive instances" do
-        instance_inactive.group_ids_access_map = {
+        group_access_instance_inactive.group_ids_access_map = {
           group_full.id => ['full'],
           group_read.id => ['read'],
         }
 
-        expect(instance_inactive.group_ids_access_map).to be_empty
+        expect(group_access_instance_inactive.group_ids_access_map).to be_empty
       end
     end
 
@@ -380,8 +380,8 @@ RSpec.shared_examples 'HasGroups' do
           group_read.id => ['read'],
         }
 
-        instance.associations_from_param(group_ids: expected)
-        expect(instance.group_ids_access_map).to eq(expected)
+        group_access_instance.associations_from_param(group_ids: expected)
+        expect(group_access_instance.group_ids_access_map).to eq(expected)
       end
 
       it 'handles groups parameter as group_names_access_map' do
@@ -390,8 +390,8 @@ RSpec.shared_examples 'HasGroups' do
           group_read.name => ['read'],
         }
 
-        instance.associations_from_param(groups: expected)
-        expect(instance.group_names_access_map).to eq(expected)
+        group_access_instance.associations_from_param(groups: expected)
+        expect(group_access_instance.group_names_access_map).to eq(expected)
       end
     end
 
@@ -403,9 +403,9 @@ RSpec.shared_examples 'HasGroups' do
           group_read.id => ['read'],
         }
 
-        instance.group_ids_access_map = expected
+        group_access_instance.group_ids_access_map = expected
 
-        result = instance.attributes_with_association_ids
+        result = group_access_instance.attributes_with_association_ids
         expect(result['group_ids']).to eq(expected)
       end
     end
@@ -418,9 +418,9 @@ RSpec.shared_examples 'HasGroups' do
           group_read.id => ['read'],
         }
 
-        instance.group_ids_access_map = expected
+        group_access_instance.group_ids_access_map = expected
 
-        result = instance.attributes_with_association_names
+        result = group_access_instance.attributes_with_association_names
         expect(result['group_ids']).to eq(expected)
       end
 
@@ -430,77 +430,77 @@ RSpec.shared_examples 'HasGroups' do
           group_read.name => ['read'],
         }
 
-        instance.group_names_access_map = expected
+        group_access_instance.group_names_access_map = expected
 
-        result = instance.attributes_with_association_names
+        result = group_access_instance.attributes_with_association_names
         expect(result['groups']).to eq(expected)
       end
     end
 
-    context '.group_access_ids' do
+    context '.group_access' do
+
+      it 'responds to group_access' do
+        expect(described_class).to respond_to(:group_access)
+      end
 
       before(:each) do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_read.name => 'read',
         }
       end
 
-      it 'responds to group_access_ids' do
-        expect(described_class).to respond_to(:group_access_ids)
-      end
-
-      it 'lists only active instance IDs' do
-        instance_inactive.group_names_access_map = {
+      it 'lists only active instances' do
+        group_access_instance_inactive.group_names_access_map = {
           group_read.name => 'read',
         }
 
-        result = described_class.group_access_ids(group_read.id, 'read')
-        expect(result).not_to include(instance_inactive.id)
+        result = described_class.group_access(group_read.id, 'read')
+        expect(result).not_to include(group_access_instance_inactive)
       end
 
       context 'Group ID parameter' do
-        include_examples '.group_access_ids call' do
+        include_examples '.group_access call' do
           let(:group_parameter) { group_read.id }
         end
       end
 
       context 'Group parameter' do
-        include_examples '.group_access_ids call' do
-          let(:group_parameter) { group_read.id }
+        include_examples '.group_access call' do
+          let(:group_parameter) { group_read }
         end
       end
     end
 
-    context '.group_access' do
+    context '.group_access_ids' do
 
-      it 'responds to group_access' do
-        expect(described_class).to respond_to(:group_access)
+      it 'responds to group_access_ids' do
+        expect(described_class).to respond_to(:group_access_ids)
       end
 
-      it 'wraps .group_access_ids' do
-        expect(described_class).to receive(:group_access_ids)
-        described_class.group_access(group_read, 'read')
+      it 'wraps .group_access' do
+        expect(described_class).to receive(:group_access).and_call_original
+        described_class.group_access_ids(group_read, 'read')
       end
 
       it 'returns class instances' do
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_read.name => 'read',
         }
 
-        result = described_class.group_access(group_read, 'read')
-        expect(result).to include(instance)
+        result = described_class.group_access_ids(group_read, 'read')
+        expect(result).to include(group_access_instance.id)
       end
     end
 
     it 'destroys relations before instance gets destroyed' do
 
-      instance.group_names_access_map = {
+      group_access_instance.group_names_access_map = {
         group_full.name     => 'full',
         group_read.name     => 'read',
         group_inactive.name => 'write',
       }
       expect do
-        instance.destroy
+        group_access_instance.destroy
       end.to change {
         described_class.group_through.klass.count
       }.by(-3)
@@ -512,46 +512,46 @@ RSpec.shared_examples '#group_access? call' do
   context 'single access' do
 
     it 'checks positive' do
-      expect(instance.group_access?(group_parameter, 'read')).to be true
+      expect(group_access_instance.group_access?(group_parameter, 'read')).to be true
     end
 
     it 'checks negative' do
-      expect(instance.group_access?(group_parameter, 'write')).to be false
+      expect(group_access_instance.group_access?(group_parameter, 'write')).to be false
     end
   end
 
   context 'access list' do
 
     it 'checks positive' do
-      expect(instance.group_access?(group_parameter, %w(read write))).to be true
+      expect(group_access_instance.group_access?(group_parameter, %w(read write))).to be true
     end
 
     it 'checks negative' do
-      expect(instance.group_access?(group_parameter, %w(write create))).to be false
+      expect(group_access_instance.group_access?(group_parameter, %w(write create))).to be false
     end
   end
 end
 
-RSpec.shared_examples '.group_access_ids call' do
+RSpec.shared_examples '.group_access call' do
   context 'single access' do
 
     it 'lists access IDs' do
-      expect(described_class.group_access_ids(group_parameter, 'read')).to include(instance.id)
+      expect(described_class.group_access(group_parameter, 'read')).to include(group_access_instance)
     end
 
     it 'excludes non access IDs' do
-      expect(described_class.group_access_ids(group_parameter, 'write')).not_to include(instance.id)
+      expect(described_class.group_access(group_parameter, 'write')).not_to include(group_access_instance)
     end
   end
 
   context 'access list' do
 
     it 'lists access IDs' do
-      expect(described_class.group_access_ids(group_parameter, %w(read write))).to include(instance.id)
+      expect(described_class.group_access(group_parameter, %w(read write))).to include(group_access_instance)
     end
 
     it 'excludes non access IDs' do
-      expect(described_class.group_access_ids(group_parameter, %w(write create))).not_to include(instance.id)
+      expect(described_class.group_access(group_parameter, %w(write create))).not_to include(group_access_instance)
     end
   end
 end

+ 79 - 0
spec/models/concerns/has_groups_permissions_examples.rb

@@ -0,0 +1,79 @@
+# Requires: let(:group_access_no_permission_instance) { ... }
+RSpec.shared_examples 'HasGroups and Permissions' do
+
+  context 'group' do
+
+    let(:group_read) { create(:group) }
+
+    before(:each) do
+      group_access_no_permission_instance.group_names_access_map = {
+        group_read.name => 'read',
+      }
+    end
+
+    context '#group_access?' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.group_access?(group_read, 'read')).to be false
+      end
+    end
+
+    context '#group_ids_access' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.group_ids_access('read')).to be_empty
+      end
+    end
+
+    context '#groups_access' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.groups_access('read')).to be_empty
+      end
+    end
+
+    context '#group_names_access_map' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.group_names_access_map).to be_empty
+      end
+    end
+
+    context '#group_ids_access_map' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.group_ids_access_map).to be_empty
+      end
+    end
+
+    context '#attributes_with_association_ids' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.attributes_with_association_ids['group_ids']).to be_empty
+      end
+    end
+
+    context '#attributes_with_association_names' do
+
+      it 'prevents instances without permissions' do
+        expect(group_access_no_permission_instance.attributes_with_association_names['group_ids']).to be_empty
+      end
+    end
+
+    context '.group_access' do
+
+      it 'prevents instances without permissions' do
+        result = described_class.group_access(group_read.id, 'read')
+        expect(result).not_to include(group_access_no_permission_instance)
+      end
+    end
+
+    context '.group_access_ids' do
+
+      it 'prevents instances without permissions' do
+        result = described_class.group_access(group_read.id, 'read')
+        expect(result).not_to include(group_access_no_permission_instance.id)
+      end
+    end
+  end
+end

+ 70 - 67
spec/models/concerns/has_roles_examples.rb

@@ -1,10 +1,13 @@
+# Requires: let(:group_access_instance) { ... }
+# Requires: let(:new_group_access_instance) { ... }
 RSpec.shared_examples 'HasRoles' do
 
   context 'role' do
 
-    let(:factory_name) { described_class.name.downcase.to_sym }
-    let(:instance) { create(factory_name) }
-    let(:instance_inactive) { create(factory_name, active: false) }
+    let(:group_access_instance_inactive) {
+      group_access_instance.update_attribute(:active, false)
+      group_access_instance
+    }
     let(:role) { create(:role) }
     let(:group_instance) { create(:group) }
     let(:group_role) { create(:group) }
@@ -12,36 +15,39 @@ RSpec.shared_examples 'HasRoles' do
 
     context '#role_access?' do
 
-      before(:each) do
-        role.group_names_access_map = {
-          group_role.name => 'read',
-        }
-
-        instance.roles = [role]
-      end
-
       it 'responds to role_access?' do
-        expect(instance).to respond_to(:role_access?)
+        expect(group_access_instance).to respond_to(:role_access?)
       end
 
-      context 'Group ID parameter' do
-        include_examples '#role_access? call' do
-          let(:group_parameter) { group_role.id }
+      context 'active Role' do
+        before(:each) do
+          role.group_names_access_map = {
+            group_role.name => 'read',
+          }
+
+          group_access_instance.roles.push(role)
+          group_access_instance.save
         end
-      end
 
-      context 'Group parameter' do
-        include_examples '#role_access? call' do
-          let(:group_parameter) { group_role }
+        context 'Group ID parameter' do
+          include_examples '#role_access? call' do
+            let(:group_parameter) { group_role.id }
+          end
         end
-      end
 
-      it 'prevents inactive Group' do
-        role.group_names_access_map = {
-          group_inactive.name => 'read',
-        }
+        context 'Group parameter' do
+          include_examples '#role_access? call' do
+            let(:group_parameter) { group_role }
+          end
+        end
 
-        expect(instance.group_access?(group_inactive.id, 'read')).to be false
+        it 'prevents inactive Group' do
+          role.group_names_access_map = {
+            group_inactive.name => 'read',
+          }
+
+          expect(group_access_instance.group_access?(group_inactive.id, 'read')).to be false
+        end
       end
 
       it 'prevents inactive Role' do
@@ -50,9 +56,10 @@ RSpec.shared_examples 'HasRoles' do
           group_role.name => 'read',
         }
 
-        instance.roles = [role_inactive]
+        group_access_instance.roles.push(role_inactive)
+        group_access_instance.save
 
-        expect(instance.group_access?(group_role.id, 'read')).to be false
+        expect(group_access_instance.group_access?(group_role.id, 'read')).to be false
       end
     end
 
@@ -63,7 +70,8 @@ RSpec.shared_examples 'HasRoles' do
           group_role.name => 'read',
         }
 
-        instance.roles = [role]
+        group_access_instance.roles.push(role)
+        group_access_instance.save
       end
 
       it 'responds to role_access_ids' do
@@ -75,19 +83,12 @@ RSpec.shared_examples 'HasRoles' do
           group_role.name => 'read',
         }
 
-        result = described_class.group_access_ids(group_role.id, 'read')
-        expect(result).not_to include(instance_inactive.id)
-      end
-
-      it 'lists only active instance IDs' do
-        role.group_names_access_map = {
-          group_role.name => 'read',
-        }
-
-        instance_inactive.roles = [role]
+        group_access_instance_inactive.roles.push(role)
+        group_access_instance_inactive.save
+        group_access_instance_inactive.save
 
         result = described_class.role_access_ids(group_role.id, 'read')
-        expect(result).not_to include(instance_inactive.id)
+        expect(result).not_to include(group_access_instance_inactive.id)
       end
 
       context 'Group ID parameter' do
@@ -98,7 +99,7 @@ RSpec.shared_examples 'HasRoles' do
 
       context 'Group parameter' do
         include_examples '.role_access_ids call' do
-          let(:group_parameter) { group_role.id }
+          let(:group_parameter) { group_role }
         end
       end
     end
@@ -110,9 +111,10 @@ RSpec.shared_examples 'HasRoles' do
           group_role.name => 'read',
         }
 
-        instance.roles = [role]
+        group_access_instance.roles.push(role)
+        group_access_instance.save
 
-        instance.group_names_access_map = {
+        group_access_instance.group_names_access_map = {
           group_instance.name => 'read',
         }
       end
@@ -120,13 +122,13 @@ RSpec.shared_examples 'HasRoles' do
       context '#group_access?' do
 
         it 'falls back to #role_access?' do
-          expect(instance).to receive(:role_access?)
-          instance.group_access?(group_role, 'read')
+          expect(group_access_instance).to receive(:role_access?)
+          group_access_instance.group_access?(group_role, 'read')
         end
 
         it "doesn't fall back to #role_access? if not needed" do
-          expect(instance).not_to receive(:role_access?)
-          instance.group_access?(group_instance, 'read')
+          expect(group_access_instance).not_to receive(:role_access?)
+          group_access_instance.group_access?(group_instance, 'read')
         end
       end
 
@@ -137,9 +139,10 @@ RSpec.shared_examples 'HasRoles' do
             group_role.name => 'read',
           }
 
-          instance.roles = [role]
+          group_access_instance.roles.push(role)
+          group_access_instance.save
 
-          instance.group_names_access_map = {
+          group_access_instance.group_names_access_map = {
             group_instance.name => 'read',
           }
         end
@@ -150,28 +153,28 @@ RSpec.shared_examples 'HasRoles' do
             group_inactive.name => 'read',
           }
 
-          result = instance.group_ids_access('read')
+          result = group_access_instance.group_ids_access('read')
           expect(result).not_to include(group_inactive.id)
         end
 
         context 'single access' do
 
           it 'lists access Group IDs' do
-            result = instance.group_ids_access('read')
+            result = group_access_instance.group_ids_access('read')
             expect(result).to include(group_role.id)
           end
 
           it "doesn't list for no access" do
-            result = instance.group_ids_access('write')
+            result = group_access_instance.group_ids_access('write')
             expect(result).not_to include(group_role.id)
           end
 
           it "doesn't contain duplicate IDs" do
-            instance.group_names_access_map = {
+            group_access_instance.group_names_access_map = {
               group_role.name => 'read',
             }
 
-            result = instance.group_ids_access('read')
+            result = group_access_instance.group_ids_access('read')
             expect(result.uniq).to eq(result)
           end
         end
@@ -179,21 +182,21 @@ RSpec.shared_examples 'HasRoles' do
         context 'access list' do
 
           it 'lists access Group IDs' do
-            result = instance.group_ids_access(%w(read write))
+            result = group_access_instance.group_ids_access(%w(read write))
             expect(result).to include(group_role.id)
           end
 
           it "doesn't list for no access" do
-            result = instance.group_ids_access(%w(write create))
+            result = group_access_instance.group_ids_access(%w(write create))
             expect(result).not_to include(group_role.id)
           end
 
           it "doesn't contain duplicate IDs" do
-            instance.group_names_access_map = {
+            group_access_instance.group_names_access_map = {
               group_role.name => 'read',
             }
 
-            result = instance.group_ids_access(%w(read create))
+            result = group_access_instance.group_ids_access(%w(read create))
             expect(result.uniq).to eq(result)
           end
         end
@@ -203,11 +206,11 @@ RSpec.shared_examples 'HasRoles' do
 
         it 'includes the result of .role_access_ids' do
           result = described_class.group_access_ids(group_role, 'read')
-          expect(result).to include(instance.id)
+          expect(result).to include(group_access_instance.id)
         end
 
         it "doesn't contain duplicate IDs" do
-          instance.group_names_access_map = {
+          group_access_instance.group_names_access_map = {
             group_role.name => 'read',
           }
 
@@ -223,22 +226,22 @@ RSpec.shared_examples '#role_access? call' do
   context 'single access' do
 
     it 'checks positive' do
-      expect(instance.role_access?(group_parameter, 'read')).to be true
+      expect(group_access_instance.role_access?(group_parameter, 'read')).to be true
     end
 
     it 'checks negative' do
-      expect(instance.role_access?(group_parameter, 'write')).to be false
+      expect(group_access_instance.role_access?(group_parameter, 'write')).to be false
     end
   end
 
   context 'access list' do
 
     it 'checks positive' do
-      expect(instance.role_access?(group_parameter, %w(read write))).to be true
+      expect(group_access_instance.role_access?(group_parameter, %w(read write))).to be true
     end
 
     it 'checks negative' do
-      expect(instance.role_access?(group_parameter, %w(write create))).to be false
+      expect(group_access_instance.role_access?(group_parameter, %w(write create))).to be false
     end
   end
 end
@@ -247,22 +250,22 @@ RSpec.shared_examples '.role_access_ids call' do
   context 'single access' do
 
     it 'lists access IDs' do
-      expect(described_class.role_access_ids(group_parameter, 'read')).to include(instance.id)
+      expect(described_class.role_access_ids(group_parameter, 'read')).to include(group_access_instance.id)
     end
 
     it 'excludes non access IDs' do
-      expect(described_class.role_access_ids(group_parameter, 'write')).not_to include(instance.id)
+      expect(described_class.role_access_ids(group_parameter, 'write')).not_to include(group_access_instance.id)
     end
   end
 
   context 'access list' do
 
     it 'lists access IDs' do
-      expect(described_class.role_access_ids(group_parameter, %w(read write))).to include(instance.id)
+      expect(described_class.role_access_ids(group_parameter, %w(read write))).to include(group_access_instance.id)
     end
 
     it 'excludes non access IDs' do
-      expect(described_class.role_access_ids(group_parameter, %w(write create))).not_to include(instance.id)
+      expect(described_class.role_access_ids(group_parameter, %w(write create))).not_to include(group_access_instance.id)
     end
   end
 end

+ 3 - 0
spec/models/role_spec.rb

@@ -2,5 +2,8 @@ require 'rails_helper'
 require 'models/concerns/has_groups_examples'
 
 RSpec.describe Role do
+  let(:group_access_instance) { create(:role) }
+  let(:new_group_access_instance) { build(:role) }
+
   include_examples 'HasGroups'
 end

+ 7 - 0
spec/models/user_spec.rb

@@ -1,10 +1,17 @@
 require 'rails_helper'
 require 'models/concerns/has_groups_examples'
 require 'models/concerns/has_roles_examples'
+require 'models/concerns/has_groups_permissions_examples'
 
 RSpec.describe User do
+
+  let(:group_access_instance) { create(:user, roles: [Role.find_by(name: 'Agent')]) }
+  let(:new_group_access_instance) { build(:user, roles: [Role.find_by(name: 'Agent')]) }
+  let(:group_access_no_permission_instance) { build(:user) }
+
   include_examples 'HasGroups'
   include_examples 'HasRoles'
+  include_examples 'HasGroups and Permissions'
 
   let(:new_password) { 'N3W54V3PW!' }