|
# we need permissions to be seeded already
|
|
require Rails.root + 'db/seeds.d/20-permissions'
|
|
|
|
# Fake models to make sure that this migration can be executed even when
|
|
# original models changes later (e.g. add validation on columns that are not
|
|
# present at this moment)
|
|
class FakePermission < ActiveRecord::Base
|
|
set_table_name 'permissions'
|
|
end
|
|
|
|
class FakeFilter < ActiveRecord::Base
|
|
set_table_name 'filters'
|
|
# we need this for polymorphic relation to work, it has class name hardcoded in AR
|
|
def self.name
|
|
'Filter'
|
|
end
|
|
belongs_to :role, :class_name => 'FakeRole'
|
|
has_many :filterings, :dependent => :destroy, :foreign_key => 'filter_id'
|
|
has_many :permissions, :through => :filterings
|
|
|
|
def resource_type
|
|
@resource_type ||= permissions.first.try(:resource_type)
|
|
end
|
|
|
|
taxonomy_join_table = "taxable_taxonomies"
|
|
has_many taxonomy_join_table, :dependent => :destroy, :as => :taxable, :foreign_key => 'taxable_id'
|
|
has_many :locations, :through => taxonomy_join_table, :source => :taxonomy,
|
|
:conditions => "taxonomies.type='Location'", :validate => false
|
|
has_many :organizations, :through => taxonomy_join_table, :source => :taxonomy,
|
|
:conditions => "taxonomies.type='Organization'", :validate => false
|
|
|
|
end
|
|
|
|
class FakeUserRole < ActiveRecord::Base
|
|
set_table_name 'user_roles'
|
|
belongs_to :owner, :polymorphic => true
|
|
belongs_to :role, :class_name => 'FakeRole'
|
|
end
|
|
|
|
class FakeRole < ActiveRecord::Base
|
|
set_table_name 'roles'
|
|
scope :builtin, lambda { |*args|
|
|
compare = 'not' if args.first
|
|
where("#{compare} builtin = 0")
|
|
}
|
|
|
|
has_many :filters, :dependent => :destroy, :class_name => 'FakeFilter', :foreign_key => 'role_id'
|
|
has_many :permissions, :through => :filters, :class_name => 'FakePermission', :foreign_key => 'permission_id'
|
|
end
|
|
|
|
class FakeFiltering < ActiveRecord::Base
|
|
set_table_name 'filterings'
|
|
belongs_to :filter, :class_name => 'FakeFilter'
|
|
belongs_to :permission, :class_name => 'FakePermission'
|
|
end
|
|
|
|
class FakeUser < ActiveRecord::Base
|
|
set_table_name 'users'
|
|
# we need this for polymorphic relation to work, it has class name hardcoded in AR
|
|
def self.name
|
|
'User'
|
|
end
|
|
|
|
has_and_belongs_to_many :compute_resources, :join_table => "user_compute_resources", :foreign_key => 'user_id'
|
|
has_and_belongs_to_many :domains, :join_table => "user_domains", :foreign_key => 'user_id'
|
|
has_many :user_hostgroups, :dependent => :destroy, :foreign_key => 'user_id'
|
|
has_many :hostgroups, :through => :user_hostgroups
|
|
has_many :user_facts, :dependent => :destroy, :foreign_key => 'user_id'
|
|
has_many :facts, :through => :user_facts, :source => :fact_name
|
|
has_many :user_roles, :dependent => :destroy, :foreign_key => 'owner_id',
|
|
:conditions => {:owner_type => 'User'}, :class_name => 'FakeUserRole'
|
|
has_many :roles, :through => :user_roles, :dependent => :destroy, :class_name => 'FakeRole'
|
|
taxonomy_join_table = "taxable_taxonomies"
|
|
has_many taxonomy_join_table, :dependent => :destroy, :as => :taxable, :foreign_key => 'taxable_id'
|
|
has_many :locations, :through => taxonomy_join_table, :source => :taxonomy,
|
|
:conditions => "taxonomies.type='Location'", :validate => false
|
|
has_many :organizations, :through => taxonomy_join_table, :source => :taxonomy,
|
|
:conditions => "taxonomies.type='Organization'", :validate => false
|
|
has_many :cached_usergroup_members, :foreign_key => 'user_id'
|
|
has_many :cached_usergroups, :through => :cached_usergroup_members, :source => :usergroup
|
|
end
|
|
|
|
class MigratePermissions < ActiveRecord::Migration
|
|
def self.up
|
|
if old_permissions_present
|
|
migrate_roles
|
|
migrate_user_filters
|
|
|
|
flag = Setting::General.find_or_initialize_by_name('fix_db_cache',
|
|
:description => 'Fix DB cache on next Foreman restart',
|
|
:settings_type => 'boolean', :default => false)
|
|
flag.update_attributes :value => true
|
|
Rake::Task['db:migrate'].enhance nil do
|
|
Rake::Task['fix_db_cache'].invoke
|
|
end
|
|
else
|
|
say 'Skipping migration of permissions, since old permissions are not present'
|
|
end
|
|
end
|
|
|
|
# STEP 1 - migrate roles
|
|
# for all role permissions we'll create unlimited filters
|
|
# we'll group permissions into filters by their resource
|
|
def self.migrate_roles
|
|
roles = FakeRole.all
|
|
roles.each do |role|
|
|
|
|
# role without permissions? nothing to do then
|
|
if role.attributes['permissions'].nil?
|
|
say "no old permissions found for role '#{role.name}', skipping"
|
|
next
|
|
end
|
|
|
|
# permissions assigned to role which we want to migrate
|
|
permission_names = YAML.load(role.attributes['permissions'])
|
|
|
|
# role without permissions but with YAML record
|
|
if permission_names.blank?
|
|
clear_old_permission(role)
|
|
next
|
|
end
|
|
|
|
# filter out unknown permissions, this could be leftovers from an old plugin.
|
|
role_permissions = FakePermission.where(:name => permission_names)
|
|
|
|
# we group permissions by resource the belong to
|
|
# then create a filter per resource
|
|
# and create a new relation between mapped permission and this filter
|
|
role_permissions.group_by(&:resource_type).each do |resource, permissions|
|
|
filter = FakeFilter.new
|
|
filter.role = role
|
|
filter.save!
|
|
say "Created an unlimited filter for role '#{role.name}'"
|
|
|
|
permissions.each do |permission|
|
|
filtering = FakeFiltering.new
|
|
filtering.filter = filter
|
|
filtering.permission = FakePermission.find_by_name(permission.name)
|
|
filtering.save!
|
|
say "... with permission '#{permission.name}'"
|
|
end
|
|
end
|
|
|
|
# finally we clear old permissions from role so
|
|
clear_old_permission(role)
|
|
end
|
|
end
|
|
|
|
def self.clear_old_permission(role)
|
|
say "Clearing old permissions for role '#{role.name}'"
|
|
if FakeRole.update_all("permissions = NULL", "id = #{role.id}") == 1
|
|
say "... OK"
|
|
else
|
|
raise "could not clear old permissions for role '#{role.name}'"
|
|
end
|
|
end
|
|
|
|
# STEP 2 - migrate user filters
|
|
# for every user having a filter we make copy of all his roles and add filtering scoped searches
|
|
# to corresponding filters
|
|
def self.migrate_user_filters
|
|
users = FakeUser.all
|
|
users.each do |user|
|
|
unless filtered?(user)
|
|
say "no filters found for user '#{user.login}', skipping"
|
|
next
|
|
end
|
|
|
|
say "Migrating user '#{user.login}'"
|
|
say "... cloning all roles"
|
|
clones = user.roles.builtin(false).map { |r| clone_role(r, user) }
|
|
user.roles = clones + user.roles.builtin(true)
|
|
say "... done"
|
|
|
|
filters = Hash.new { |h, k| h[k] = '' }
|
|
|
|
# compute resources
|
|
filters[:compute_resources] = search = user.compute_resources.uniq.map { |cr| "id = #{cr.id}" }.join(' or ')
|
|
affected = clones.map(&:filters).flatten.select { |f| f.resource_type == 'ComputeResource' }
|
|
affected.each do |filter|
|
|
filter.update_attributes :search => search unless search.blank?
|
|
end
|
|
say "... compute resource filters applied"
|
|
|
|
# domains were not limited in old system, to keep it compatible, we don't convert it and use just search string
|
|
# later for hosts
|
|
filters[:domains] = user.domains.uniq.map { |cr| "id = #{cr.id}" }.join(' or ')
|
|
|
|
# host groups
|
|
filters[:hostgroups] = search = user.hostgroups.uniq.map { |cr| "id = #{cr.id}" }.join(' or ')
|
|
affected = clones.map(&:filters).flatten.select { |f| f.resource_type == 'Hostgroup' }
|
|
affected.each do |filter|
|
|
filter.update_attributes :search => search unless search.blank?
|
|
end
|
|
say "... hostgroups filters applied"
|
|
|
|
# fact_values for hosts scope
|
|
filters[:facts] = user.user_facts.uniq.map { |uf| "facts.#{uf.fact_name.name} #{uf.operator} #{uf.criteria}" }.join(' or ')
|
|
|
|
search, orgs, locs = convert_filters_to_search(filters, user)
|
|
|
|
affected = clones.map(&:filters).flatten.select { |f| f.resource_type == 'Host' }
|
|
affected.each do |filter|
|
|
filter.organizations = orgs
|
|
filter.locations = locs
|
|
filter.update_attributes :search => search unless search.blank?
|
|
end
|
|
say "... all other filters applied"
|
|
|
|
say "Removing old filter"
|
|
user.domains = []
|
|
user.compute_resources = []
|
|
user.hostgroups = []
|
|
user.facts = []
|
|
user.filter_on_owner = false
|
|
user.save!
|
|
say "... done"
|
|
end
|
|
end
|
|
|
|
def self.convert_filters_to_search(filters, user)
|
|
search = ''
|
|
orgs = []
|
|
locs = []
|
|
|
|
# owner_type
|
|
if user.filter_on_owner
|
|
user_cond = "owner_id = #{user.id} and owner_type = User"
|
|
group_cond = user.cached_usergroups.uniq.map { |g| "owner_id = #{g.id}" }.join(' or ')
|
|
search = "(#{user_cond})"
|
|
search += " or ((#{group_cond}) and owner_type = Usergroup)" unless group_cond.blank?
|
|
end
|
|
|
|
# normal filters - domains, compute resource, hostgroup, facts
|
|
filter = filters[:domains].gsub('id', 'domain_id')
|
|
search = "(#{search}) #{user.domains_andor} (#{filter})" unless filter.blank?
|
|
filter = filters[:compute_resources].gsub('id', 'compute_resource_id')
|
|
search = "(#{search}) #{user.compute_resources_andor} (#{filter})" unless filter.blank?
|
|
filter = filters[:hostgroups].gsub('id', 'hostgroup_id')
|
|
search = "(#{search}) #{user.hostgroups_andor} (#{filter})" unless filter.blank?
|
|
filter = filters[:facts]
|
|
search = "(#{search}) #{user.facts_andor} (#{filter})" unless filter.blank?
|
|
|
|
# taxonomies
|
|
if SETTINGS[:organizations_enabled]
|
|
orgs = user.organizations
|
|
end
|
|
if SETTINGS[:locations_enabled]
|
|
locs = user.locations
|
|
end
|
|
|
|
# fix first and/or that could appear
|
|
search = search.sub(/^\(\)\s*(and|or)\s*/, '')
|
|
[ search, orgs, locs ]
|
|
end
|
|
|
|
def self.filtered?(user)
|
|
user.compute_resources.present? ||
|
|
user.domains.present? ||
|
|
user.hostgroups.present? ||
|
|
user.facts.present? ||
|
|
user.filter_on_owner
|
|
end
|
|
|
|
def self.clone_role(role, user)
|
|
clone = role.dup
|
|
clone.name = role.name + "_#{user.login}"
|
|
clone.save!
|
|
|
|
role.filters.each { |f| clone_filter(f, clone) }
|
|
|
|
clone.reload
|
|
end
|
|
|
|
def self.clone_filter(filter, role)
|
|
clone = filter.dup
|
|
clone.permissions = filter.permissions
|
|
clone.role = role
|
|
clone.save!
|
|
end
|
|
|
|
|
|
# To detect whether migration is needed we use existing models
|
|
# fakes would always indicate that migration is needed
|
|
def self.old_permissions_present
|
|
user = User.new
|
|
Role.column_names.include?('permissions') &&
|
|
user.respond_to?(:compute_resources) &&
|
|
user.respond_to?(:domains) &&
|
|
user.respond_to?(:hostgroups) &&
|
|
user.respond_to?(:facts) &&
|
|
user.respond_to?(:filter_on_owner)
|
|
end
|
|
|
|
def self.down
|
|
say 'Permission data migration is impossible, skipping'
|
|
end
|
|
end
|