[PATCH conductor 1/4] permissions denormalization: redmine #3155 and #3156

Scott Seago sseago at redhat.com
Tue May 8 20:50:18 UTC 2012


---
 src/app/controllers/deployables_controller.rb      |    1 +
 src/app/models/base_permission_object.rb           |   16 ++++++
 src/app/models/catalog.rb                          |   19 ++++++-
 src/app/models/catalog_entry.rb                    |   11 ++++-
 src/app/models/deployable.rb                       |    8 ++-
 src/app/models/deployment.rb                       |   14 +++++-
 .../{permission.rb => derived_permission.rb}       |   43 ++++++---------
 src/app/models/hardware_profile.rb                 |    4 ++
 src/app/models/instance.rb                         |   10 +++-
 src/app/models/permission.rb                       |   32 +++++++++++
 src/app/models/permissioned_object.rb              |   51 ++++++++++++++++--
 src/app/models/pool.rb                             |   16 +++++-
 src/app/models/pool_family.rb                      |   21 +++++++
 src/app/models/privilege.rb                        |    8 ++--
 src/app/models/provider.rb                         |   12 ++++
 src/app/models/provider_account.rb                 |   10 +++-
 src/app/models/role.rb                             |    8 +++
 src/app/models/user.rb                             |    1 +
 .../20120424151000_create_derived_permissions.rb   |   37 +++++++++++++
 src/spec/models/deployable_spec.rb                 |   57 ++++++++++++++++++++
 src/spec/models/deployment_spec.rb                 |    4 +-
 src/spec/models/instance_observer_spec.rb          |    1 +
 22 files changed, 340 insertions(+), 44 deletions(-)
 copy src/app/models/{permission.rb => derived_permission.rb} (75%)
 create mode 100644 src/db/migrate/20120424151000_create_derived_permissions.rb

diff --git a/src/app/controllers/deployables_controller.rb b/src/app/controllers/deployables_controller.rb
index 8967e79..911e337 100644
--- a/src/app/controllers/deployables_controller.rb
+++ b/src/app/controllers/deployables_controller.rb
@@ -191,6 +191,7 @@ class DeployablesController < ApplicationController
     end
   end
 
+  # the name here is confusing; we may want to rename to multi_remove at some point
   def multi_destroy
     deleted = []
     not_deleted = []
diff --git a/src/app/models/base_permission_object.rb b/src/app/models/base_permission_object.rb
index 8133a7f..ce25861 100644
--- a/src/app/models/base_permission_object.rb
+++ b/src/app/models/base_permission_object.rb
@@ -31,6 +31,9 @@ class BasePermissionObject < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
 
   validates_presence_of :name
   validates_uniqueness_of :name
@@ -42,4 +45,17 @@ class BasePermissionObject < ActiveRecord::Base
     base_permission = self.create!(:name => GENERAL_PERMISSION_SCOPE) unless base_permission
     base_permission
   end
+
+  def derived_subtree(role = nil)
+    subtree = super(role)
+    [Catalog, Deployable, PoolFamily, Pool, Deployment,
+     Instance, Provider, ProviderAccount, HardwareProfile].each do |obj_type|
+      subtree += obj_type.all if (role.nil? or role.privilege_target_match(obj_type))
+    end
+    subtree
+  end
+  def self.additional_privilege_target_types
+    [HardwareProfile, Catalog, Deployable, PoolFamily, Pool,
+     Deployment, Instance, Provider, ProviderAccount]
+  end
 end
diff --git a/src/app/models/catalog.rb b/src/app/models/catalog.rb
index 1f3ac9a..5ab51c4 100644
--- a/src/app/models/catalog.rb
+++ b/src/app/models/catalog.rb
@@ -38,18 +38,30 @@ class Catalog < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
 
   before_destroy :destroy_deployables_related_only_to_self
   before_create :set_pool_family
+  after_update :update_deployable_permissions
 
   validates_presence_of :pool
   validates_presence_of :name
   validates_uniqueness_of :name
   validates_length_of :name, :maximum => 1024
 
-  def object_list
+  def perm_ancestors
     super + [pool, pool_family]
   end
+  def derived_subtree(role = nil)
+    subtree = super(role)
+    subtree += deployables if (role.nil? or role.privilege_target_match(Deployable))
+    subtree
+  end
+  def self.additional_privilege_target_types
+    [Deployable]
+  end
   class << self
     alias orig_list_for_user_include list_for_user_include
     alias orig_list_for_user_conditions list_for_user_conditions
@@ -80,6 +92,11 @@ class Catalog < ActiveRecord::Base
     self[:pool_family_id] = pool.pool_family_id
   end
 
+  def update_deployable_permissions
+    update_derived_permissions_for_ancestors
+    deployables.each {|d| d.update_derived_permissions_for_ancestors}
+  end
+
   private
 
   def self.apply_search_filter(search)
diff --git a/src/app/models/catalog_entry.rb b/src/app/models/catalog_entry.rb
index 857d013..03461d2 100644
--- a/src/app/models/catalog_entry.rb
+++ b/src/app/models/catalog_entry.rb
@@ -36,9 +36,18 @@ class CatalogEntry < ActiveRecord::Base
   validates_uniqueness_of :catalog_id, :scope => [:deployable_id]
 
   after_destroy :destroy_deployable_if_last
+  after_create :update_deployable_permissions
+
+  def update_deployable_permissions
+    deployable.update_derived_permissions_for_ancestors
+  end
 
   def destroy_deployable_if_last
-    deployable.destroy if deployable.catalog_entries.blank?
+    if deployable.catalog_entries.blank?
+      deployable.destroy
+    else
+      deployable.update_derived_permissions_for_ancestors
+    end
   end
 
   # This probably goes away once we separate catalog entry creation from deployables
diff --git a/src/app/models/deployable.rb b/src/app/models/deployable.rb
index 5f4157e..02c53ff 100644
--- a/src/app/models/deployable.rb
+++ b/src/app/models/deployable.rb
@@ -34,6 +34,10 @@ class Deployable < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
+
   has_many :catalog_entries, :dependent => :destroy
   has_many :catalogs, :through => :catalog_entries
   belongs_to :pool_family
@@ -49,8 +53,8 @@ class Deployable < ActiveRecord::Base
 
   PRESET_FILTERS_OPTIONS = []
 
-  def object_list
-    super + catalogs + catalogs.collect{|c| c.pool} + [pool_family]
+  def perm_ancestors
+    super + catalogs + catalogs.collect{|c| c.pool}.uniq + [pool_family]
   end
   class << self
     alias orig_list_for_user_include list_for_user_include
diff --git a/src/app/models/deployment.rb b/src/app/models/deployment.rb
index ecd857c..54d879a 100644
--- a/src/app/models/deployment.rb
+++ b/src/app/models/deployment.rb
@@ -52,6 +52,10 @@ class Deployment < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
+
   belongs_to :owner, :class_name => "User", :foreign_key => "owner_id"
 
   has_many :events, :as => :source, :dependent => :destroy
@@ -118,9 +122,17 @@ class Deployment < ActiveRecord::Base
     errors.add(:pool, I18n.t('pools.errors.providers_disabled')) if pool and pool.pool_family.all_providers_disabled?
   end
 
-  def object_list
+  def perm_ancestors
     super + [pool, pool_family]
   end
+  def derived_subtree(role = nil)
+    subtree = super(role)
+    subtree += instances if (role.nil? or role.privilege_target_match(Instance))
+    subtree
+  end
+  def self.additional_privilege_target_types
+    [Instance]
+  end
   class << self
     alias orig_list_for_user_include list_for_user_include
     alias orig_list_for_user_conditions list_for_user_conditions
diff --git a/src/app/models/permission.rb b/src/app/models/derived_permission.rb
similarity index 75%
copy from src/app/models/permission.rb
copy to src/app/models/derived_permission.rb
index ccda5f7..06e7e8e 100644
--- a/src/app/models/permission.rb
+++ b/src/app/models/derived_permission.rb
@@ -14,32 +14,12 @@
 #   limitations under the License.
 #
 
-# == Schema Information
-# Schema version: 20110207110131
-#
-# Table name: permissions
-#
-#  id                     :integer         not null, primary key
-#  role_id                :integer         not null
-#  user_id                :integer         not null
-#  permission_object_id   :integer
-#  permission_object_type :string(255)
-#  lock_version           :integer         default(0)
-#  created_at             :datetime
-#  updated_at             :datetime
-#
-
-class Permission < ActiveRecord::Base
-  belongs_to :role
-  belongs_to :user
-
-  validates_presence_of :role_id
-
-  validates_presence_of :user_id
-  validates_uniqueness_of :user_id, :scope => [:permission_object_id,
-                                               :permission_object_type,
-                                               :role_id]
+class DerivedPermission < ActiveRecord::Base
+  # the source permission for the denormalized object
+  belongs_to :permission
+  validates_presence_of :permission_id
 
+  # this is the object used for permission checks
   belongs_to :permission_object,      :polymorphic => true
   # type-specific associations
   belongs_to :pool_family,            :class_name => "PoolFamily",
@@ -58,7 +38,20 @@ class Permission < ActiveRecord::Base
                                       :foreign_key => "permission_object_id"
   belongs_to :provider_account,          :class_name => "ProviderAccount",
                                       :foreign_key => "permission_object_id"
+  belongs_to :hardware_profile,       :class_name => "hardwareProfile",
+                                      :foreign_key => "permission_object_id"
   belongs_to :base_permission_object, :class_name => "BasePermissionObject",
                                       :foreign_key => "permission_object_id"
 
+  # role is copied from source permission
+  belongs_to :role
+  validates_presence_of :role_id
+
+  # user is copied from source permission
+  belongs_to :user
+  validates_presence_of :user_id
+
+  validates_uniqueness_of :permission_id, :scope => [:permission_object_id,
+                                                     :permission_object_type]
+
 end
diff --git a/src/app/models/hardware_profile.rb b/src/app/models/hardware_profile.rb
index 9c325c1..2099214 100644
--- a/src/app/models/hardware_profile.rb
+++ b/src/app/models/hardware_profile.rb
@@ -41,6 +41,10 @@ class HardwareProfile < ActiveRecord::Base
   end
   include PermissionedObject
   has_many :permissions, :as => :permission_object, :dependent => :destroy
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
+
   has_many :instances
   scope :frontend, :conditions => { :provider_id => nil }
   has_many :provider_instances, :class_name => "Instance",
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb
index 062372e..84f1224 100644
--- a/src/app/models/instance.rb
+++ b/src/app/models/instance.rb
@@ -80,6 +80,10 @@ class Instance < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
+
   has_many :events, :as => :source, :dependent => :destroy
   has_many :instance_parameters, :dependent => :destroy
   has_many :tasks, :as =>:task_target, :dependent => :destroy
@@ -141,8 +145,10 @@ class Instance < ActiveRecord::Base
   # changed if called from another Aeolus component, so attr_protected isn't quite what we want:
   USER_MUTABLE_ATTRS = ['name']
 
-  def object_list
-    super + [deployment, pool, pool_family]
+  def perm_ancestors
+    ancestors = super
+    ancestors << deployment unless deployment.nil?
+    ancestors += [pool, pool_family]
   end
   class << self
     alias orig_list_for_user_include list_for_user_include
diff --git a/src/app/models/permission.rb b/src/app/models/permission.rb
index ccda5f7..89b2268 100644
--- a/src/app/models/permission.rb
+++ b/src/app/models/permission.rb
@@ -58,7 +58,39 @@ class Permission < ActiveRecord::Base
                                       :foreign_key => "permission_object_id"
   belongs_to :provider_account,          :class_name => "ProviderAccount",
                                       :foreign_key => "permission_object_id"
+  belongs_to :hardware_profile,       :class_name => "hardwareProfile",
+                                      :foreign_key => "permission_object_id"
   belongs_to :base_permission_object, :class_name => "BasePermissionObject",
                                       :foreign_key => "permission_object_id"
 
+  has_many :derived_permissions, :dependent => :destroy
+
+  after_save :update_derived_permissions
+
+  def update_derived_permissions
+    new_derived_permission_objects = permission_object.derived_subtree(role)
+    old_derived_permissions = derived_permissions
+    old_derived_permissions.each do |derived_perm|
+      if new_derived_permission_objects.delete(derived_perm.permission_object)
+        # object is in both old and new list -- update as necessary
+        derived_perm.role = role
+        derived_perm.user_id = user_id
+        derived_perm.save!
+      else
+        # object is in old but not new list -- remove it
+        derived_perm.destroy
+      end
+    end
+    new_derived_permission_objects.each do |perm_obj|
+      unless DerivedPermission.where(:permission_id => id,
+                                     :permission_object_id => perm_obj.id,
+                                     :permission_object_type => perm_obj.class.name).any?
+        derived_perm = DerivedPermission.new(:user_id => user_id,
+                                             :role_id => role_id,
+                                             :permission_object => perm_obj,
+                                             :permission => self)
+        derived_perm.save!
+      end
+    end
+  end
 end
diff --git a/src/app/models/permissioned_object.rb b/src/app/models/permissioned_object.rb
index c534261..3f083ea 100644
--- a/src/app/models/permissioned_object.rb
+++ b/src/app/models/permissioned_object.rb
@@ -19,7 +19,7 @@ module PermissionedObject
   def has_privilege(user, action, target_type=nil)
     return false if user.nil? or action.nil?
     target_type = self.class.default_privilege_target_type if target_type.nil?
-    object_list.each do |obj|
+    perm_ancestors.each do |obj|
       return true if obj and obj.permissions.find(:first,
                                           :include => [:role => :privileges],
                                           :conditions =>
@@ -35,10 +35,39 @@ module PermissionedObject
 
   # Returns the list of objects to check for permissions on -- by default
   # this object plus the Base permission object
-  def object_list
+  # At the moment, the retuned list is not necessarily ordered
+  # FIXME: remove self once has_privilege stops using this method
+  def perm_ancestors
     [self, BasePermissionObject.general_permission_scope]
   end
-
+  # Returns the list of objects to generate derived permissions for
+  # -- by default just this object
+  def derived_subtree(role = nil)
+    [self]
+  end
+  # on obj creation, set inherited permissions for new object
+  def update_derived_permissions_for_ancestors
+    # for create hook this should normally be empty
+    old_derived_permissions = Hash[derived_permissions.map{|p| [p.permission.id,p]}]
+    perm_ancestors.each do |perm_obj|
+      perm_obj.permissions.each do |permission|
+        if permission.role.privilege_target_match(self.class.default_privilege_target_type)
+          unless old_derived_permissions.delete(permission.id)
+            derived_permissions.create(:user_id => permission.user_id,
+                                       :role_id => permission.role_id,
+                                       :permission => permission)
+          end
+        end
+      end
+    end
+    # anything remaining in old_derived_permissions should be removed,
+    # as would be expected if this hook is triggered by removing a
+    # catalog entry for a deployable
+    old_derived_permissions.each do |id, derived_perm|
+      derived_perm.destroy
+    end
+    #reload
+  end
   # assign owner role so that the creating user has permissions on the object
   # Any roles defined on default_privilege_target_type with assign_to_owner==true
   # will be assigned to the passed-in user on this object
@@ -49,14 +78,28 @@ module PermissionedObject
     roles.each do |role|
       Permission.create!(:role => role, :user => user, :permission_object => self)
     end
+    self.reload
   end
 
   # Any methods here will be able to use the context of the
   # ActiveRecord model the module is included in.
   def self.included(base)
     base.class_eval do
+      after_create :update_derived_permissions_for_ancestors
+
+      # Returns the list of privilege target types that are relevant for
+      # permission checking purposes. This is used in setting derived
+      # permissions -- there's no need to create denormalized permissions
+      # for a role which only grants Provider privileges on a Pool
+      # object. By default, this is just the current object's type
+      def self.active_privilege_target_types
+        [self.default_privilege_target_type] + self.additional_privilege_target_types
+      end
+      def self.additional_privilege_target_types
+        []
+      end
       def self.default_privilege_target_type
-        self.name.constantize
+        self
       end
       def self.list_for_user_include
         [:permissions]
diff --git a/src/app/models/pool.rb b/src/app/models/pool.rb
index 4604bd2..9110481 100644
--- a/src/app/models/pool.rb
+++ b/src/app/models/pool.rb
@@ -60,6 +60,9 @@ class Pool < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
 
   has_many :deployments, :dependent => :destroy
 
@@ -148,9 +151,20 @@ class Pool < ActiveRecord::Base
     {:title => "pools.preset_filters.with_stopped_instances", :id => "with_stopped_instances", :query => includes(:deployments => :instances).where("instances.state" => "stopped")}
   ]
 
-  def object_list
+  def perm_ancestors
     super + [pool_family]
   end
+  def derived_subtree(role = nil)
+    subtree = super(role)
+    subtree += deployments if (role.nil? or role.privilege_target_match(Deployment))
+    subtree += instances if (role.nil? or role.privilege_target_match(Instance))
+    subtree += catalogs if (role.nil? or role.privilege_target_match(Deployable))
+    subtree += catalogs.collect {|c| c.deployables}.flatten.uniq if (role.nil? or role.privilege_target_match(Deployable))
+    subtree
+  end
+  def self.additional_privilege_target_types
+    [Deployment, Instance, Catalog, Quota]
+  end
   class << self
     alias orig_list_for_user_include list_for_user_include
     alias orig_list_for_user_conditions list_for_user_conditions
diff --git a/src/app/models/pool_family.rb b/src/app/models/pool_family.rb
index dd474a9..60fb0cb 100644
--- a/src/app/models/pool_family.rb
+++ b/src/app/models/pool_family.rb
@@ -43,6 +43,14 @@ class PoolFamily < ActiveRecord::Base
   accepts_nested_attributes_for :quota
   has_and_belongs_to_many :provider_accounts, :uniq => true, :order => "provider_accounts.priority asc"
   has_many :permissions, :as => :permission_object, :dependent => :destroy
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
+
+  has_many :catalogs
+  has_many :deployables
+  has_many :instances
+  has_many :deployments
 
   validates_length_of :name, :maximum => 255
   validates_format_of :name, :with => /^[\w -]*$/n, :message => "must only contain: numbers, letters, spaces, '_' and '-'"
@@ -61,6 +69,19 @@ class PoolFamily < ActiveRecord::Base
     MetadataObject.set(DEFAULT_POOL_FAMILY_KEY, self)
   end
 
+  def derived_subtree(role = nil)
+    subtree = super(role)
+    subtree += pools if (role.nil? or role.privilege_target_match(Pool))
+    subtree += deployments if (role.nil? or role.privilege_target_match(Deployment))
+    subtree += instances if (role.nil? or role.privilege_target_match(Instance))
+    subtree += catalogs if (role.nil? or role.privilege_target_match(Deployable))
+    subtree += deployables if (role.nil? or role.privilege_target_match(Deployable))
+    subtree
+  end
+  def self.additional_privilege_target_types
+    [Pool, Quota]
+  end
+
   def destroyable?
     # A PoolFamily is destroyable if its pools are destroyable and it is not  the default PoolFamily
     pools.all? {|p| p.destroyable? } && self != PoolFamily.default
diff --git a/src/app/models/privilege.rb b/src/app/models/privilege.rb
index ae1f038..1d857db 100644
--- a/src/app/models/privilege.rb
+++ b/src/app/models/privilege.rb
@@ -51,14 +51,14 @@ class Privilege < ActiveRecord::Base
               PERM_SET, PERM_VIEW]
   TYPES   = { BasePermissionObject => [MODIFY, PERM_SET, PERM_VIEW],
               Pool => ACTIONS - [USE],
-              PoolFamily => ACTIONS - [USE],
+              PoolFamily => ACTIONS,
               Instance => ACTIONS,
               Deployment => ACTIONS,
-              CatalogEntry => ACTIONS,
+              Deployable => ACTIONS,
               Quota => [VIEW, MODIFY],
-              HardwareProfile => ACTIONS - [USE, VIEW],
+              HardwareProfile => ACTIONS - [USE],
               Realm => ACTIONS - [VIEW],
-              Provider => ACTIONS - [USE],
+              Provider => ACTIONS,
               ProviderAccount => ACTIONS,
               User => [ CREATE, MODIFY, VIEW] }
 
diff --git a/src/app/models/provider.rb b/src/app/models/provider.rb
index fc3040e..ea9749b 100644
--- a/src/app/models/provider.rb
+++ b/src/app/models/provider.rb
@@ -56,9 +56,21 @@ class Provider < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
 
   before_destroy :destroyable?
 
+  def derived_subtree(role = nil)
+    subtree = super(role)
+    subtree += provider_accounts if (role.nil? or role.privilege_target_match(ProviderAccount))
+    subtree
+  end
+  def self.additional_privilege_target_types
+    [ProviderAccount]
+  end
+
   scope :enabled, where("enabled = ?", true)
 
   def encoded_url_with_driver_and_provider
diff --git a/src/app/models/provider_account.rb b/src/app/models/provider_account.rb
index 232928e..0ebc848 100644
--- a/src/app/models/provider_account.rb
+++ b/src/app/models/provider_account.rb
@@ -48,6 +48,9 @@ class ProviderAccount < ActiveRecord::Base
   has_many :permissions, :as => :permission_object, :dependent => :destroy,
            :include => [:role],
            :order => "permissions.id ASC"
+  has_many :derived_permissions, :as => :permission_object, :dependent => :destroy,
+           :include => [:role],
+           :order => "derived_permissions.id ASC"
 
   # validation of credentials is done in provider_account validation, :validate => false prevents nested_attributes from validation
   has_many :credentials, :dependent => :destroy, :validate => false
@@ -106,8 +109,11 @@ class ProviderAccount < ActiveRecord::Base
     return true
   end
 
-  def object_list
-    super << provider
+  def perm_ancestors
+    super + [provider]
+  end
+  def self.additional_privilege_target_types
+    [Quota]
   end
   class << self
     alias orig_list_for_user_include list_for_user_include
diff --git a/src/app/models/role.rb b/src/app/models/role.rb
index 98f0a5a..dcf3178 100644
--- a/src/app/models/role.rb
+++ b/src/app/models/role.rb
@@ -30,6 +30,7 @@
 
 class Role < ActiveRecord::Base
   has_many :permissions, :dependent => :destroy
+  has_many :derived_permissions, :dependent => :destroy
   has_many :privileges, :dependent => :destroy
 
   validates_presence_of :scope
@@ -39,4 +40,11 @@ class Role < ActiveRecord::Base
   validates_associated :privileges
 
   validates_length_of :name, :maximum => 255
+
+  def privilege_target_types
+    privileges.collect {|x| Kernel.const_get(x.target_type)}.uniq
+  end
+  def privilege_target_match(obj_type)
+    (privilege_target_types & obj_type.active_privilege_target_types).any?
+  end
 end
diff --git a/src/app/models/user.rb b/src/app/models/user.rb
index c2822ea..66565a7 100644
--- a/src/app/models/user.rb
+++ b/src/app/models/user.rb
@@ -59,6 +59,7 @@ class User < ActiveRecord::Base
   attr_accessor :ignore_password
 
   has_many :permissions, :dependent => :destroy
+  has_many :derived_permissions, :dependent => :destroy
   has_many :owned_instances, :class_name => "Instance", :foreign_key => "owner_id"
   has_many :deployments, :foreign_key => "owner_id"
   has_many :view_states
diff --git a/src/db/migrate/20120424151000_create_derived_permissions.rb b/src/db/migrate/20120424151000_create_derived_permissions.rb
new file mode 100644
index 0000000..318d320
--- /dev/null
+++ b/src/db/migrate/20120424151000_create_derived_permissions.rb
@@ -0,0 +1,37 @@
+#
+#   Copyright 2012 Red Hat, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+class CreateDerivedPermissions < ActiveRecord::Migration
+  def self.up
+    create_table :derived_permissions do |t|
+      t.integer :permission_id, :null => false
+      t.integer :role_id, :null => false
+      t.integer :user_id, :null => false
+      t.integer :permission_object_id
+      t.string  :permission_object_type
+      t.integer :lock_version, :default => 0
+
+      t.timestamps
+    end
+    # create derived permissions for existing records
+    Permission.all.each do |permission|
+      permission.update_derived_permissions
+    end
+  end
+
+  def self.down
+    drop_table :derived_permissions
+  end
+end
diff --git a/src/spec/models/deployable_spec.rb b/src/spec/models/deployable_spec.rb
index 6b32968..a98eed7 100644
--- a/src/spec/models/deployable_spec.rb
+++ b/src/spec/models/deployable_spec.rb
@@ -71,4 +71,61 @@ describe Deployable do
     deployable.xml = ''
     deployable.should_not be_valid
   end
+
+  it "should manage permissions on catalog changes" do
+    pool1 = FactoryGirl.create :pool
+    pool2 = FactoryGirl.create :pool
+    catalog1 = FactoryGirl.create :catalog #, :pool => :pool1
+    catalog2 = FactoryGirl.create :catalog
+    catalog1.pool.should_not == catalog2.pool
+    admin = FactoryGirl.create :admin_user
+    pool1_perm = Permission.create(:user => admin,
+                                   :role => Role.first(:conditions =>
+                                                   ['name = ?', 'pool.admin']),
+                                     :permission_object => catalog1.pool)
+    pool2_perm = Permission.create(:user => admin,
+                                   :role => Role.first(:conditions =>
+                                        ['name = ?', 'pool.deployable.admin']),
+                                     :permission_object => catalog2.pool)
+    deployable = FactoryGirl.create :deployable, :catalogs => [catalog1]
+    catalog1.reload
+    catalog2.reload
+    catalog1.pool.permissions.should == [pool1_perm]
+    catalog2.pool.permissions.should == [pool2_perm]
+
+    catalog1.derived_permissions.collect {|p|
+      p.role.name}.should == ["pool.admin"]
+    catalog2.derived_permissions.collect {|p|
+      p.role.name}.should == ["pool.deployable.admin"]
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.admin").should be_true
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.deployable.admin").should be_false
+    deployable.catalogs.should == [catalog1]
+
+    deployable.catalogs << catalog2
+    deployable.reload
+    deployable.catalogs.should == [catalog1, catalog2]
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.admin").should be_true
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.deployable.admin").should be_true
+
+    deployable.catalog_entries.where(:catalog_id => catalog1.id).first.destroy
+    deployable.reload
+    deployable.catalogs.should == [catalog2]
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.admin").should be_false
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.deployable.admin").should be_true
+
+    catalog2.pool = catalog1.pool
+    catalog2.save
+    deployable.reload
+    deployable.catalogs.should == [catalog2]
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.admin").should be_true
+    deployable.derived_permissions.collect {|p|
+      p.role.name}.include?("pool.deployable.admin").should be_false
+  end
 end
diff --git a/src/spec/models/deployment_spec.rb b/src/spec/models/deployment_spec.rb
index f382eda..7706345 100644
--- a/src/spec/models/deployment_spec.rb
+++ b/src/spec/models/deployment_spec.rb
@@ -96,7 +96,7 @@ describe Deployment do
       @deployment.save!
       inst1 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
       inst2 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
-
+      @deployment.reload
       @deployment.should_not be_destroyable
       @deployment.destroy.should == false
 
@@ -117,6 +117,7 @@ describe Deployment do
       inst2.state = Instance::STATE_STOPPED
       inst2.save!
       inst3 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
+      @deployment.reload
       Taskomatic.stub!(:stop_instance).and_return(nil)
       @deployment.stop_instances_and_destroy!
       inst1.tasks.last.action.should == 'stop'
@@ -297,6 +298,7 @@ describe Deployment do
       @deployment.save!
       inst1 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
       inst2 = Factory.create :mock_running_instance, :deployment_id => @deployment.id
+      @deployment.reload
 
       @deployment.stop_instances_and_destroy!
 
diff --git a/src/spec/models/instance_observer_spec.rb b/src/spec/models/instance_observer_spec.rb
index d014077..9cc70be 100644
--- a/src/spec/models/instance_observer_spec.rb
+++ b/src/spec/models/instance_observer_spec.rb
@@ -209,6 +209,7 @@ describe InstanceObserver do
 
   it "should destroy the instance key when the instance is stopped" do
     @instance = FactoryGirl.create :mock_running_instance
+    @instance_key = FactoryGirl.create :mock_instance_key, :instance => @instance
     @instance.instance_key.should_not be_nil
     @instance.state = Instance::STATE_STOPPED
     @instance.save!
-- 
1.7.6.5




More information about the aeolus-devel mailing list