Skip to content

Commit

Permalink
Merge pull request #12369 from mkanoor/rbac_tres
Browse files Browse the repository at this point in the history
RBAC support for Automate Service Models
  • Loading branch information
gmcculloug authored Dec 5, 2016
2 parents 9dfc42a + 346be98 commit b2cd625
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 6 deletions.
11 changes: 11 additions & 0 deletions lib/miq_automation_engine/engine/drb_remote_invoker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ def with_server(inputs, body)
teardown if num_methods == 0
end

# This method is called by the client thread that runs for each request
# coming into the server.
# See https://github.com/ruby/ruby/blob/trunk/lib/drb/drb.rb#L1658
# Previously we had used DRb.front but that gets compromised when multiple
# DRb servers are running in the same process.
def self.workspace
if Thread.current['DRb'] && Thread.current['DRb']['server']
Thread.current['DRb']['server'].front.workspace
end
end

private

# invocation
Expand Down
5 changes: 5 additions & 0 deletions lib/miq_automation_engine/engine/miq_ae_service.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require_relative 'miq_ae_service/miq_ae_service_model_legacy'
require_relative 'miq_ae_service/miq_ae_service_object_common'
require_relative 'miq_ae_service/miq_ae_service_vmdb'
require_relative 'miq_ae_service/miq_ae_service_rbac'
module MiqAeMethodService
class Deprecation < Vmdb::Deprecation
def self.default_log
Expand All @@ -25,6 +26,7 @@ class MiqAeService
include DRbUndumped
include MiqAeMethodService::MiqAeServiceModelLegacy
include MiqAeMethodService::MiqAeServiceVmdb
include MiqAeMethodService::MiqAeServiceRbac

attr_accessor :logger

Expand Down Expand Up @@ -59,8 +61,11 @@ def initialize(ws, inputs = {}, body = nil, logger = $miq_ae_logger)
@persist_state_hash = ws.persist_state_hash
@logger = logger
self.class.add(self)
ws.disable_rbac
end

delegate :enable_rbac, :disable_rbac, :rbac_enabled?, :to => :@workspace

def stdout
@stdout ||= Vmdb::Loggers::IoLogger.new(logger, :info, "Method STDOUT:")
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module MiqAeMethodService
module MiqAeServiceRbac
extend ActiveSupport::Concern

module ClassMethods
def find_ar_object_by_id(id)
if rbac_enabled?
Rbac.filtered(model.where(:id => id), :user => workspace.ae_user).first
else
model.find(*id)
end
end

def all
objs = rbac_enabled? ? Rbac.filtered(model, :user => workspace.ae_user) : model.all
wrap_results(objs)
end

def count
rbac_enabled? ? Rbac.filtered(model, :user => workspace.ae_user).count : model.count
end

def first
objs = rbac_enabled? ? Rbac.filtered(model, :user => workspace.ae_user, :limit => 1) : model
wrap_results(objs.first)
end

def filter_objects(objs)
if objs.nil?
objs
elsif objs.kind_of?(Array) || objs.kind_of?(ActiveRecord::Relation)
rbac_enabled? ? Rbac.filtered(objs, :user => workspace.ae_user) : objs
else
rbac_enabled? ? Rbac.filtered_object(objs, :user => workspace.ae_user) : objs
end
end

def workspace
MiqAeEngine::MiqAeWorkspaceRuntime.current || MiqAeEngine::DrbRemoteInvoker.workspace
end

def rbac_enabled?
workspace && workspace.rbac_enabled?
end
end
end
end
9 changes: 5 additions & 4 deletions lib/miq_automation_engine/engine/miq_ae_service_model_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ class << self
include DRbUndumped # Ensure that Automate Method can get at instances over DRb
include MiqAeServiceObjectCommon
include Vmdb::Logging
include MiqAeMethodService::MiqAeServiceRbac

def self.method_missing(m, *args)
return wrap_results(model.send(m, *args)) if class_method_exposed?(m)
return wrap_results(filter_objects(model.send(m, *args))) if class_method_exposed?(m)
super
rescue ActiveRecord::RecordNotFound
raise MiqAeException::ServiceNotFound, "Service Model not found"
Expand All @@ -35,7 +36,7 @@ def self.allowed_find_method?(m)

# Expose the ActiveRecord find, all, count, and first
def self.class_method_exposed?(m)
allowed_find_method?(m.to_s) || [:where, :find, :all, :count, :first].include?(m)
allowed_find_method?(m.to_s) || [:where, :find].include?(m)
end

private_class_method :class_method_exposed?
Expand Down Expand Up @@ -121,7 +122,7 @@ def self.expose(*args)
method = options[:method] || method_name
ret = object_send(method, *params)
return options[:override_return] if options.key?(:override_return)
wrap_results(ret)
wrap_results(self.class.filter_objects(ret))
end
end
end
Expand Down Expand Up @@ -197,7 +198,7 @@ def initialize(obj)
if obj.kind_of?(ActiveRecord::Base) && !obj.kind_of?(ar_klass)
raise ArgumentError.new("#{ar_klass.name} Object expected, but received #{obj.class.name}")
end
@object = obj.kind_of?(ar_klass) ? obj : ar_method { ar_klass.find_by_id(obj.to_i) }
@object = obj.kind_of?(ar_klass) ? obj : ar_method { self.class.find_ar_object_by_id(obj.to_i) }
raise MiqAeException::ServiceNotFound, "#{ar_klass.name} Object [#{obj}] not found" if @object.nil?
end

Expand Down
28 changes: 28 additions & 0 deletions lib/miq_automation_engine/engine/miq_ae_workspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def initialize(options = {})
@current_state_info = {}
@state_machine_objects = []
@ae_user = nil
@rbac = false
end

delegate :prepend_namespace=, :to => :@dom_search
Expand All @@ -60,12 +61,39 @@ def readonly?
@readonly
end

def self.current=(ws)
Thread.current[:current_workspace] = ws
end

def self.current
Thread.current[:current_workspace]
end

def self.clear_stored_workspace
self.current = nil
end

def self.instantiate(uri, user, attrs = {})
User.current_user = user
workspace = MiqAeWorkspaceRuntime.new(attrs)
workspace.instantiate(uri, user, nil)
self.current = workspace
workspace
rescue MiqAeException
ensure
clear_stored_workspace
end

def rbac_enabled?
@rbac
end

def enable_rbac
@rbac = true
end

def disable_rbac
@rbac = false
end

DATASTORE_CACHE = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ module DrbRemoteInvokerSpec
include MiqAeEngine
describe MiqAeEngine::DrbRemoteInvoker do
it "setup/teardown drb_for_ruby_method clears DRb threads" do
invoker = described_class.new(double("workspace", :persist_state_hash => {}))
workspace = double("workspace", :persist_state_hash => {})
allow(workspace).to receive(:disable_rbac).with(no_args)
invoker = described_class.new(workspace)

timer_thread = nil

Expand Down
3 changes: 3 additions & 0 deletions spec/lib/miq_automation_engine/engine/miq_ae_method_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

def persist_state_hash
end

def disable_rbac
end
end.new

logger_klass = Class.new do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module MiqAeServiceModelSpec
include MiqAeEngine
describe MiqAeMethodService::MiqAeServiceVmOrTemplate do
include Spec::Support::AutomationHelper
before do
vm11
vm21
user1
user2
end

let(:options) { {} }

let(:default_tenant) { Tenant.seed }

let(:tenant1) { FactoryGirl.create(:tenant) }
let(:group1) { FactoryGirl.create(:miq_group, :tenant => tenant1) }
let(:ems1) { FactoryGirl.create(:ext_management_system, :tenant => tenant1) }
let(:host1) { FactoryGirl.create(:host) }
let(:user1) { FactoryGirl.create(:user, :miq_groups => [group1], :settings => {:display => {:timezone => "UTC"}}) }
let(:vm11) { FactoryGirl.create(:vm_vmware, :tenant => tenant1, :host => host1, :miq_group => group1) }
let(:vm12) { FactoryGirl.create(:vm_vmware, :tenant => tenant1, :host => host1, :miq_group => group1) }
let(:vm13) { FactoryGirl.create(:vm_vmware, :tenant => tenant1, :host => host1, :miq_group => group1) }

let(:tenant2) { FactoryGirl.create(:tenant) }
let(:group2) { FactoryGirl.create(:miq_group, :tenant => tenant2) }
let(:user2) { FactoryGirl.create(:user, :miq_groups => [group2], :settings => {:display => {:timezone => "UTC"}}) }
let(:ems2) { FactoryGirl.create(:ext_management_system, :tenant => tenant2) }
let(:host2) { FactoryGirl.create(:host) }
let(:vm21) { FactoryGirl.create(:vm_vmware, :tenant => tenant2, :host => host2, :miq_group => group2) }
let(:vm22) { FactoryGirl.create(:vm_vmware, :tenant => tenant2, :host => host2, :miq_group => group2) }
let(:vm23) { FactoryGirl.create(:vm_vmware, :tenant => tenant2, :host => host2, :miq_group => group2) }

context "automate methods - enable rbac" do
def collect_ids_with_rbac
<<-'RUBY'
$evm.enable_rbac
$evm.root['vm_ids'] = $evm.vmdb('vm').all.collect(&:id)
RUBY
end

it 'filter all vms for a user via method with rbac' do
vm12
vm13
vm22
create_ae_model_with_method(:name => 'FLINTSTONE', :ae_namespace => 'FRED',
:ae_class => 'WILMA', :instance_name => 'DOGMATIX',
:method_name => 'OBELIX',
:method_script => collect_ids_with_rbac)
ws = MiqAeEngine.instantiate("/FRED/WILMA/DOGMATIX", user2)
ids = [vm21.id, vm22.id]
expect(ws.root("vm_ids")).to match_array(ids)
end

after do
MiqAeEngine::MiqAeWorkspaceRuntime.current = nil
end
end

context "disable rbac - automate method" do
def collect_ids_without_rbac
<<-'RUBY'
# RBAC is disabled by default
# $evm.disable_rbac
$evm.root['vm_ids'] = $evm.vmdb('vm').all.collect(&:id)
RUBY
end

it 'filter all vms for a user via method without rbac' do
vm12
vm13
vm22
create_ae_model_with_method(:name => 'FLINTSTONE', :ae_namespace => 'FRED',
:ae_class => 'WILMA', :instance_name => 'DOGMATIX',
:method_name => 'OBELIX',
:method_script => collect_ids_without_rbac)
ws = MiqAeEngine.instantiate("/FRED/WILMA/DOGMATIX", user2)
ids = [vm11.id, vm21.id, vm22.id, vm12.id, vm13.id]
expect(ws.root("vm_ids")).to match_array(ids)
end

after do
MiqAeEngine::MiqAeWorkspaceRuntime.current = nil
end
end
end
end
13 changes: 12 additions & 1 deletion spec/lib/miq_automation_engine/miq_ae_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,29 @@ module MiqAeServiceSpec

describe MiqAeService do
context "#service_model" do
let(:miq_ae_service) { MiqAeService.new(double('ws', :persist_state_hash => {})) }
let(:workspace) { double('ws', :persist_state_hash => {}) }
let(:miq_ae_service) { MiqAeService.new(workspace) }
let(:prefix) { "MiqAeMethodService::MiqAeService" }

it "loads base model" do
allow(workspace).to receive(:disable_rbac)
expect(miq_ae_service.service_model(:VmOrTemplate)).to be(MiqAeMethodService::MiqAeServiceVmOrTemplate)
expect(miq_ae_service.service_model(:vm_or_template)).to be(MiqAeMethodService::MiqAeServiceVmOrTemplate)
end

it "loads sub-classed model" do
allow(workspace).to receive(:disable_rbac)
expect(miq_ae_service.service_model(:Vm)).to be(MiqAeMethodService::MiqAeServiceVm)
expect(miq_ae_service.service_model(:vm)).to be(MiqAeMethodService::MiqAeServiceVm)
end

it "loads model with mapped name" do
allow(workspace).to receive(:disable_rbac)
expect(miq_ae_service.service_model(:ems)).to be(MiqAeMethodService::MiqAeServiceExtManagementSystem)
end

it "loads name-spaced model by mapped name" do
allow(workspace).to receive(:disable_rbac)
MiqAeMethodService::Deprecation.silence do
expect(miq_ae_service.service_model(:ems_openstack)).to be(
MiqAeMethodService::MiqAeServiceManageIQ_Providers_Openstack_CloudManager)
Expand All @@ -68,23 +73,27 @@ module MiqAeServiceSpec
end

it "loads name-spaced model by fully-qualified name" do
allow(workspace).to receive(:disable_rbac)
expect(miq_ae_service.service_model(:ManageIQ_Providers_Openstack_CloudManager)).to be(
MiqAeMethodService::MiqAeServiceManageIQ_Providers_Openstack_CloudManager)
expect(miq_ae_service.service_model(:ManageIQ_Providers_Openstack_CloudManager_Vm)).to be(
MiqAeMethodService::MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm)
end

it "raises error on invalid service_model name" do
allow(workspace).to receive(:disable_rbac)
expect { miq_ae_service.service_model(:invalid_model) }.to raise_error(NameError)
end

it "loads all mapped models" do
allow(workspace).to receive(:disable_rbac)
MiqAeMethodService::MiqAeService::LEGACY_MODEL_NAMES.values.each do |model_name|
expect { "MiqAeMethodService::MiqAeService#{model_name}".constantize }.to_not raise_error
end
end

it "loads cloud networks" do
allow(workspace).to receive(:disable_rbac)
items = %w(
ManageIQ_Providers_Openstack_NetworkManager_CloudNetwork
ManageIQ_Providers_Openstack_NetworkManager_CloudNetwork_Private
Expand Down Expand Up @@ -146,6 +155,7 @@ module MiqAeServiceSpec
let(:ns) { "fred" }

it "set namespace" do
allow(workspace).to receive(:disable_rbac)
allow(workspace).to receive(:persist_state_hash).and_return({})
expect(workspace).to receive(:prepend_namespace=).with(ns)

Expand All @@ -156,6 +166,7 @@ module MiqAeServiceSpec
before do
NotificationType.seed
allow(User).to receive_messages(:server_timezone => 'UTC')
allow(workspace).to receive(:disable_rbac)
end

let(:options) { {} }
Expand Down

0 comments on commit b2cd625

Please sign in to comment.