19210 - Publish the actionpolicy simple rpc authorization plugin
Refactored, added tests and updated docs
This commit is contained in:
91
README.md
91
README.md
@@ -1,4 +1,91 @@
|
|||||||
mcollective-actionpolicy-auth
|
Action Policy Authorization Plugin
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
MCollective Authorization plugin allowing fine grained ACLs
|
This is a plugin that provides fine grained action level authorization for agents.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
=============================
|
||||||
|
|
||||||
|
* Follow the [basic plugin install guide](http://projects.puppetlabs.com/projects/mcollective-plugins/wiki/InstalingPlugins) by placing actionpolicy.rb
|
||||||
|
and actionpolicy.ddl in the util directory.
|
||||||
|
|
||||||
|
Note that it is not currently possible to use the 'mco plugin package' command to package this plugin.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============================
|
||||||
|
|
||||||
|
There are three configuration options for the actionpolicy plugin
|
||||||
|
|
||||||
|
* allow_unconfigured - allow requests to agents that do not have policy files configured
|
||||||
|
* enable_default - enables a default policy file
|
||||||
|
* default_name - the name of the default policy file
|
||||||
|
|
||||||
|
General authentication configuration options can also be set in the config file.
|
||||||
|
|
||||||
|
# Enables system wide rpc authorization
|
||||||
|
rpcauthorization = 1
|
||||||
|
# Sets the authorization provider to use the actionpolicy plugin
|
||||||
|
rpcauthprovider = action_policy
|
||||||
|
|
||||||
|
Enabling a default policy
|
||||||
|
|
||||||
|
plugin.actionpolicy.enable_default = 1
|
||||||
|
plugin.actionpolicy.default_name = default
|
||||||
|
|
||||||
|
This allows you to create a policy file called default.policy which will be used unless a specific policy file exists.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Policies are defined in files like <configdir>/policies/<agent>.policy
|
||||||
|
|
||||||
|
Example: Puppet agent policy file
|
||||||
|
|
||||||
|
policy default deny
|
||||||
|
allow uid=500 * * *
|
||||||
|
allow uid=600 * customer=acme acme::devserver
|
||||||
|
allow uid=600 enable disable status customer=acme *
|
||||||
|
allow uid=700 restart (puppet().enabled=false and environment=production) or environment=development
|
||||||
|
|
||||||
|
The above policy can be described as:
|
||||||
|
|
||||||
|
* allow unix user id 500 to do all actions on all servers.
|
||||||
|
* allow unix user id 600 to do all actions on machines with the fact customer=acme and the config class acme::devserver
|
||||||
|
* allow unix user id 600 to do enable, disable and status on all other machines with fact customer=acme
|
||||||
|
* allow unix user id 700 to restart services at any time in development but in production only when Puppet has been disabled
|
||||||
|
* Everything else gets denied
|
||||||
|
|
||||||
|
The format of the userid will depend on your security plugin, other plugins might have a certificate name as caller it.
|
||||||
|
|
||||||
|
Like with actions you can space separate facts and config classes too which means all facts of classes listed has to be present on the system.
|
||||||
|
|
||||||
|
The last line in the example uses the compound statement language to do matching on facts and classes and allows any data plugin to be used.
|
||||||
|
This requires at least MCollective 2.2.x. When using data plugins in action policies you should avoid using slow ones as this will impact
|
||||||
|
the response times of agents and impact the client waiting time etc.
|
||||||
|
|
||||||
|
Using it in a specific Agent
|
||||||
|
=============================
|
||||||
|
|
||||||
|
You can now activate it in your agents:
|
||||||
|
|
||||||
|
module MCollective::Agent
|
||||||
|
class Service<RPC::Agent
|
||||||
|
authorized_by :action_policy
|
||||||
|
|
||||||
|
# ...
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
System wide configuration
|
||||||
|
=============================
|
||||||
|
|
||||||
|
You can apply this policy to all agents – but ones that specify specific policies as above will use that:
|
||||||
|
|
||||||
|
# authorization
|
||||||
|
rpcauthorization = 1
|
||||||
|
rpcauthprovider = action_policy
|
||||||
|
plugin.actionpolicy.allow_unconfigured = 1
|
||||||
|
|
||||||
|
This enables system wide authorization, tells it to use the action_policy plugin and tells it to allow agents without a policy to be used.
|
||||||
|
If you had set allow_unconfigured to 0 all requests to agents without policy files will be denied. This is configured in your server.cfg file.
|
||||||
|
|||||||
20
Rakefile
Normal file
20
Rakefile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
specdir = File.join([File.dirname(__FILE__), "spec"])
|
||||||
|
|
||||||
|
require 'rake'
|
||||||
|
begin
|
||||||
|
require 'rspec/core/rake_task'
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
|
||||||
|
if defined?(RSpec::Core::RakeTask)
|
||||||
|
desc "Run plugin tests"
|
||||||
|
RSpec::Core::RakeTask.new(:test) do |t|
|
||||||
|
require "#{specdir}/spec_helper.rb"
|
||||||
|
t.pattern = 'spec/**/*_spec.rb'
|
||||||
|
|
||||||
|
tmp_load_path = $LOAD_PATH.map { |f| f.shellescape }.join(" -I ")
|
||||||
|
t.rspec_opts = tmp_load_path + " " + File.read("#{specdir}/spec.opts").chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => :test
|
||||||
2
puppet.policy
Normal file
2
puppet.policy
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 status enable disable country=uk apache
|
||||||
510
spec/actionpolicy/actionpolicy_spec.rb
Normal file
510
spec/actionpolicy/actionpolicy_spec.rb
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
#!/bin/env rspec
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require File.join(File.dirname(__FILE__), '../../', 'util', 'actionpolicy.rb')
|
||||||
|
|
||||||
|
module MCollective
|
||||||
|
module Util
|
||||||
|
describe ActionPolicy do
|
||||||
|
let(:request) do
|
||||||
|
request = mock
|
||||||
|
request.stubs(:agent).returns('rspec_agent')
|
||||||
|
request.stubs(:caller).returns('rspec_caller')
|
||||||
|
request.stubs(:action).returns('rspec_action')
|
||||||
|
request
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:config) do
|
||||||
|
config = mock
|
||||||
|
config.stubs(:configdir).returns('/rspecdir')
|
||||||
|
config.stubs(:pluginconf).returns({})
|
||||||
|
config
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:actionpolicy) { ActionPolicy.new(request) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Config.stubs(:instance).returns(config)
|
||||||
|
@fixtures_dir = File.join(File.dirname(__FILE__), 'fixtures')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#authorize' do
|
||||||
|
it 'should create a new ActionPolicy object and call #authorize_request' do
|
||||||
|
actionpolicy.expects(:authorize_request)
|
||||||
|
ActionPolicy.expects(:new).returns(actionpolicy)
|
||||||
|
ActionPolicy.authorize(request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
it 'should set the default values' do
|
||||||
|
actionpolicy.config.should == config
|
||||||
|
actionpolicy.agent.should == 'rspec_agent'
|
||||||
|
actionpolicy.caller.should == 'rspec_caller'
|
||||||
|
actionpolicy.action.should == 'rspec_action'
|
||||||
|
actionpolicy.allow_unconfigured.should == false
|
||||||
|
actionpolicy.configdir.should == '/rspecdir'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should set allow_unconfigured if set in config file' do
|
||||||
|
config.stubs(:pluginconf).returns({'actionpolicy.allow_unconfigured' => '1'})
|
||||||
|
result = ActionPolicy.new(request)
|
||||||
|
result.allow_unconfigured.should == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#authorize_request' do
|
||||||
|
before do
|
||||||
|
Log.stubs(:debug)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should deny the request if policy file does not exist and allow_unconfigured is false' do
|
||||||
|
ActionPolicy.any_instance.expects(:lookup_policy_file).returns(nil)
|
||||||
|
|
||||||
|
expect{
|
||||||
|
actionpolicy.authorize_request
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return true if policy file does not exist but allow_unconfigured is true' do
|
||||||
|
ActionPolicy.any_instance.expects(:lookup_policy_file).returns(nil)
|
||||||
|
config.stubs(:pluginconf).returns({'actionpolicy.allow_unconfigured' => 'y'})
|
||||||
|
|
||||||
|
actionpolicy.authorize_request.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse the policy file if it exists' do
|
||||||
|
ActionPolicy.any_instance.expects(:lookup_policy_file).returns('/rspecdir/policyfile')
|
||||||
|
ActionPolicy.any_instance.expects(:parse_policy_file).with('/rspecdir/policyfile')
|
||||||
|
actionpolicy.authorize_request
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#parse_policy_file' do
|
||||||
|
|
||||||
|
before do
|
||||||
|
Log.stubs(:debug)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should deny the request if allow_unconfigured is false and no lines match' do
|
||||||
|
File.expects(:read).with('policyfile').returns('')
|
||||||
|
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file('policyfile')
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should skip comment lines' do
|
||||||
|
File.expects(:read).with('policyfile').returns('#')
|
||||||
|
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file('policyfile')
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fixtures
|
||||||
|
|
||||||
|
it 'should parse the default alllow policy' do
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'default_allow')).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse the default deny policy' do
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'default_deny'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
end
|
||||||
|
|
||||||
|
# Example fixtures
|
||||||
|
|
||||||
|
it 'should parse example1 correctly' do
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example1')).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example2 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example2')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:caller).returns('uid=501')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example2'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example3 correctly' do
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example3')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:action).returns('notrspec')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example3'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example4 correctly' do
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example4')).should be_true
|
||||||
|
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('notbar')
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example4'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example5 correctly' do
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(true)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example5')).should be_true
|
||||||
|
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(false)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example5'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example6 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example6')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:caller).returns('uid=501')
|
||||||
|
request.stubs(:action).returns('notrspec')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example6'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example7 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example7')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:caller).returns('uid=501')
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('notbar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example7'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example8 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(true)
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example8')).should be_true
|
||||||
|
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(false)
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example8'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example9 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example9')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:caller).returns('uid=501')
|
||||||
|
request.stubs(:action).returns('notrspec')
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('notbar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example9'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example10 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(true)
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:caller).returns('uid=501')
|
||||||
|
request.stubs(:action).returns('notrspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(false)
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example11 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(true)
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10')).should be_true
|
||||||
|
|
||||||
|
request.stubs(:caller).returns('uid=501')
|
||||||
|
request.stubs(:action).returns('notrspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(false)
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('notbar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
expect{
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example10'))
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example12 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('rspec').returns(true)
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
Util.stubs(:get_fact).with('bar').returns('foo')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example12')).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example13 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('one').returns(true)
|
||||||
|
Util.stubs(:has_cf_class?).with('two').returns(true)
|
||||||
|
Util.stubs(:has_cf_class?).with('three').returns(false)
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example13')).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example14 correctly' do
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
request.stubs(:action).returns('rspec')
|
||||||
|
Util.stubs(:has_cf_class?).with('one').returns(true)
|
||||||
|
Util.stubs(:has_cf_class?).with('two').returns(false)
|
||||||
|
Util.stubs(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example14')).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse example15 correctly' do
|
||||||
|
# first field
|
||||||
|
request.stubs(:caller).returns('uid=500')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
|
||||||
|
|
||||||
|
# second field
|
||||||
|
request.stubs(:caller).returns('uid=600')
|
||||||
|
Util.stubs(:get_fact).with('customer').returns('acme')
|
||||||
|
Util.stubs(:has_cf_class?).with('acme::devserver').returns(true)
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
|
||||||
|
|
||||||
|
# third field
|
||||||
|
request.stubs(:caller).returns('uid=600')
|
||||||
|
request.stubs(:action).returns('status')
|
||||||
|
Util.stubs(:get_fact).with('customer').returns('acme')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
|
||||||
|
|
||||||
|
# forth field
|
||||||
|
request.stubs(:caller).returns('uid=600')
|
||||||
|
request.stubs(:action).returns('status')
|
||||||
|
Util.stubs(:get_fact).with('customer').returns('acme')
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
|
||||||
|
|
||||||
|
# fith field
|
||||||
|
request.stubs(:caller).returns('uid=700')
|
||||||
|
request.stubs(:action).returns('restart')
|
||||||
|
Util.stubs(:get_fact).with('environment').returns('development')
|
||||||
|
Matcher.stubs(:eval_compound_fstatement).with('value' => 'enabled', 'name' => 'puppet', 'operator' => '==', 'params' => nil, 'r_compare' => 'false').returns(true)
|
||||||
|
actionpolicy = ActionPolicy.new(request)
|
||||||
|
actionpolicy.parse_policy_file(File.join(@fixtures_dir, 'example15')).should be_true
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#check_policy' do
|
||||||
|
it 'should return false if the policy line does not include the caller' do
|
||||||
|
actionpolicy.check_policy('caller', nil, nil, nil).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return false if the policy line does not include the action' do
|
||||||
|
actionpolicy.check_policy(nil, 'action', nil, nil).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse both facts and classes if callers and actions match' do
|
||||||
|
actionpolicy.expects(:parse_facts).with('*').returns(true)
|
||||||
|
actionpolicy.expects(:parse_classes).with('*').returns(true)
|
||||||
|
actionpolicy.check_policy('rspec_caller', 'rspec_action', '*', '*').should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse a compound statement if callers and actions match but classes are excluded' do
|
||||||
|
actionpolicy.expects(:parse_compound).with('*').returns(true)
|
||||||
|
actionpolicy.check_policy('rspec_caller', 'rspec_action', '*', nil).should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#parse_facts' do
|
||||||
|
it 'should return true if facts is a wildcard' do
|
||||||
|
actionpolicy.parse_facts('*').should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse compound fact statements' do
|
||||||
|
actionpolicy.stubs(:is_compound?).returns(true)
|
||||||
|
actionpolicy.expects(:parse_compound).with('foo=bar and bar=foo').returns(true)
|
||||||
|
actionpolicy.parse_facts('foo=bar and bar=foo').should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse all facts' do
|
||||||
|
actionpolicy.stubs(:is_compound?).returns(false)
|
||||||
|
actionpolicy.expects(:lookup_fact).twice.returns(true)
|
||||||
|
actionpolicy.parse_facts('foo=bar bar=foo').should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#parse_classes' do
|
||||||
|
it 'should return true if classes is a wildcard' do
|
||||||
|
actionpolicy.parse_classes('*').should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse compound class statements' do
|
||||||
|
actionpolicy.stubs(:is_compound?).returns(true)
|
||||||
|
actionpolicy.expects(:parse_compound).with('foo=bar and bar=foo').returns(true)
|
||||||
|
actionpolicy.parse_facts('foo=bar and bar=foo').should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should parse all classes' do
|
||||||
|
actionpolicy.stubs(:is_compound?).returns(false)
|
||||||
|
actionpolicy.expects(:lookup_fact).times(3).returns(true)
|
||||||
|
actionpolicy.parse_facts('foo bar baz').should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#lookup_fact' do
|
||||||
|
it 'should return false if a class is found in the fact field' do
|
||||||
|
Log.expects(:warn).with('Class found where fact was expected')
|
||||||
|
actionpolicy.lookup_fact('rspec').should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should lookup a fact value and return its true value' do
|
||||||
|
Util.expects(:get_fact).with('foo').returns('bar')
|
||||||
|
actionpolicy.lookup_fact('foo=bar').should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#lookup_class' do
|
||||||
|
it 'should return false if a fact is found in the class field' do
|
||||||
|
Log.expects(:warn).with('Fact found where class was expected')
|
||||||
|
actionpolicy.lookup_class('foo=bar').should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should lookup a fact value and return its true value' do
|
||||||
|
Util.expects(:has_cf_class?).with('rspec').returns(true)
|
||||||
|
actionpolicy.lookup_class('rspec').should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#lookup' do
|
||||||
|
it 'should call #lookup_fact if a fact was passed' do
|
||||||
|
actionpolicy.expects(:lookup_fact).with('foo=bar').returns(true)
|
||||||
|
actionpolicy.lookup('foo=bar').should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should call #lookup_class if a class was passed' do
|
||||||
|
actionpolicy.expects(:lookup_class).with('/rspec/').returns(true)
|
||||||
|
actionpolicy.lookup('/rspec/').should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#lookup_policy_file' do
|
||||||
|
before do
|
||||||
|
Log.stubs(:debug)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the path of the policyfile is present' do
|
||||||
|
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(true)
|
||||||
|
actionpolicy.lookup_policy_file.should == '/rspecdir/policies/rspec_agent.policy'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the default file path if one is specified' do
|
||||||
|
config.stubs(:pluginconf).returns({'actionpolicy.enable_default' => '1'})
|
||||||
|
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(false)
|
||||||
|
File.expects(:exist?).with('/rspecdir/policies/default.policy').returns(true)
|
||||||
|
actionpolicy.lookup_policy_file.should == '/rspecdir/policies/default.policy'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return a custom default file path if one is specified' do
|
||||||
|
config.stubs(:pluginconf).returns({'actionpolicy.enable_default' => '1',
|
||||||
|
'actionpolicy.default_name' => 'rspec'})
|
||||||
|
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(false)
|
||||||
|
File.expects(:exist?).with('/rspecdir/policies/rspec.policy').returns(true)
|
||||||
|
actionpolicy.lookup_policy_file.should == '/rspecdir/policies/rspec.policy'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil if no policy file exists' do
|
||||||
|
File.expects(:exist?).with('/rspecdir/policies/rspec_agent.policy').returns(false)
|
||||||
|
actionpolicy.lookup_policy_file.should == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#eval_statement' do
|
||||||
|
it 'should return the logical string if param is not an statement or fstatement' do
|
||||||
|
actionpolicy.eval_statement({'and' => 'and'}).should == 'and'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should lookup the value of a statement if param is a statement' do
|
||||||
|
actionpolicy.expects(:lookup).with('foo=bar').returns(true)
|
||||||
|
actionpolicy.eval_statement({'statement' => 'foo=bar'}).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should lookup the value of a data function if param is a fstatement' do
|
||||||
|
Matcher.expects(:eval_compound_fstatement).with("rspec('data').value=result").returns(true)
|
||||||
|
actionpolicy.eval_statement({'fstatement' => "rspec('data').value=result"}).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should log a failure message and return false if the fstatement cannot be parsed' do
|
||||||
|
Matcher.expects(:eval_compound_fstatement).with("rspec('data').value=result").raises('error')
|
||||||
|
Log.expects(:warn).with('Could not call Data function in policy file: error')
|
||||||
|
actionpolicy.eval_statement({'fstatement' => "rspec('data').value=result"}).should be_false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#is_compound?' do
|
||||||
|
it 'should return false if a compound statement was not identified' do
|
||||||
|
actionpolicy.is_compound?('not').should be_true
|
||||||
|
actionpolicy.is_compound?('!rspec').should be_true
|
||||||
|
actionpolicy.is_compound?('and').should be_true
|
||||||
|
actionpolicy.is_compound?('or').should be_true
|
||||||
|
actionpolicy.is_compound?("data('field').value=othervalue").should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return true if a compound statement was identified' do
|
||||||
|
actionpolicy.is_compound?('f1=v1 f1=v2').should be_false
|
||||||
|
actionpolicy.is_compound?('class1 class2 /class*/').should be_false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#deny' do
|
||||||
|
it 'should log the failure and raise an RPCAborted error' do
|
||||||
|
Log.expects(:debug).with('fail')
|
||||||
|
expect{
|
||||||
|
actionpolicy.deny('fail')
|
||||||
|
}.to raise_error RPCAborted
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
1
spec/actionpolicy/fixtures/default_allow
Normal file
1
spec/actionpolicy/fixtures/default_allow
Normal file
@@ -0,0 +1 @@
|
|||||||
|
policy default allow
|
||||||
1
spec/actionpolicy/fixtures/default_deny
Normal file
1
spec/actionpolicy/fixtures/default_deny
Normal file
@@ -0,0 +1 @@
|
|||||||
|
policy default deny
|
||||||
2
spec/actionpolicy/fixtures/example1
Normal file
2
spec/actionpolicy/fixtures/example1
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow * * * *
|
||||||
2
spec/actionpolicy/fixtures/example10
Normal file
2
spec/actionpolicy/fixtures/example10
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec * rspec
|
||||||
2
spec/actionpolicy/fixtures/example11
Normal file
2
spec/actionpolicy/fixtures/example11
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec foo=bar rspec
|
||||||
2
spec/actionpolicy/fixtures/example12
Normal file
2
spec/actionpolicy/fixtures/example12
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec foo=bar and bar=foo rspec
|
||||||
2
spec/actionpolicy/fixtures/example13
Normal file
2
spec/actionpolicy/fixtures/example13
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec foo=bar one and two or three
|
||||||
2
spec/actionpolicy/fixtures/example14
Normal file
2
spec/actionpolicy/fixtures/example14
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec foo=bar and one or (two and one)
|
||||||
5
spec/actionpolicy/fixtures/example15
Normal file
5
spec/actionpolicy/fixtures/example15
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 * * *
|
||||||
|
allow uid=600 * customer=acme acme::devserver
|
||||||
|
allow uid=600 enable disable status customer=acme *
|
||||||
|
allow uid=700 restart (puppet().enabled=false and environment=production) or environment=development
|
||||||
2
spec/actionpolicy/fixtures/example2
Normal file
2
spec/actionpolicy/fixtures/example2
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 * * *
|
||||||
2
spec/actionpolicy/fixtures/example3
Normal file
2
spec/actionpolicy/fixtures/example3
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow * rspec * *
|
||||||
2
spec/actionpolicy/fixtures/example4
Normal file
2
spec/actionpolicy/fixtures/example4
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow * * foo=bar *
|
||||||
2
spec/actionpolicy/fixtures/example5
Normal file
2
spec/actionpolicy/fixtures/example5
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow * * * rspec
|
||||||
2
spec/actionpolicy/fixtures/example6
Normal file
2
spec/actionpolicy/fixtures/example6
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec * *
|
||||||
2
spec/actionpolicy/fixtures/example7
Normal file
2
spec/actionpolicy/fixtures/example7
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 * foo=bar *
|
||||||
2
spec/actionpolicy/fixtures/example8
Normal file
2
spec/actionpolicy/fixtures/example8
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 * * rspec
|
||||||
2
spec/actionpolicy/fixtures/example9
Normal file
2
spec/actionpolicy/fixtures/example9
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
policy default deny
|
||||||
|
allow uid=500 rspec foo=bar *
|
||||||
1
spec/spec.opts
Normal file
1
spec/spec.opts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
--colour --backtrace
|
||||||
18
spec/spec_helper.rb
Normal file
18
spec/spec_helper.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
$: << File.join([File.dirname(__FILE__), "lib"])
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
require 'rspec'
|
||||||
|
require 'mcollective'
|
||||||
|
require 'mcollective/test'
|
||||||
|
require 'rspec/mocks'
|
||||||
|
require 'mocha'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.mock_with :mocha
|
||||||
|
config.include(MCollective::Test::Matchers)
|
||||||
|
|
||||||
|
config.before :each do
|
||||||
|
MCollective::PluginManager.clear
|
||||||
|
end
|
||||||
|
end
|
||||||
9
util/actionpolicy.ddl
Normal file
9
util/actionpolicy.ddl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
metadata :name => "actionpolicy",
|
||||||
|
:description => "Action Policy simplerpc authorization plugin",
|
||||||
|
:author => "P.Loubser <pieter.loubser@puppetlabs.com>",
|
||||||
|
:license => "ASL 2.0",
|
||||||
|
:version => "2.0.0",
|
||||||
|
:url => "https://github.com/puppetlabs/mcollective-actionpolicy-authorization",
|
||||||
|
:timeout => 1
|
||||||
|
|
||||||
|
requires :mcollective => "2.2.1"
|
||||||
219
util/actionpolicy.rb
Normal file
219
util/actionpolicy.rb
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
module MCollective
|
||||||
|
module Util
|
||||||
|
class ActionPolicy
|
||||||
|
attr_accessor :config, :allow_unconfigured, :configdir, :agent, :caller, :action
|
||||||
|
|
||||||
|
def self.authorize(request)
|
||||||
|
ActionPolicy.new(request).authorize_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(request)
|
||||||
|
@config = Config.instance
|
||||||
|
@agent = request.agent
|
||||||
|
@caller = request.caller
|
||||||
|
@action = request.action
|
||||||
|
@allow_unconfigured = !!(config.pluginconf.fetch('actionpolicy.allow_unconfigured', 'n') =~ /^1|y/i)
|
||||||
|
@configdir = @config.configdir
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_request
|
||||||
|
# Lookup the policy file. If none exists and @allow_unconfigured
|
||||||
|
# is false the request gets denied.
|
||||||
|
policy_file = lookup_policy_file
|
||||||
|
|
||||||
|
# No policy file exists and allow_unconfigured is false
|
||||||
|
if !policy_file && !@allow_unconfigured
|
||||||
|
deny('Could not load any valid policy files. Denying based on allow_unconfigured: %s' % @allow_unconfigured)
|
||||||
|
# No policy exists but allow_unconfigured is true
|
||||||
|
elsif !(policy_file) && @allow_unconfigured
|
||||||
|
Log.debug('Could not load any valid policy files. Allowing based on allow_unconfigured: %s' % @allow_unconfigured)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# A policy file exists
|
||||||
|
parse_policy_file(policy_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_policy_file(policy_file)
|
||||||
|
Log.debug('Parsing policyfile for %s: %s' % [@agent, policy_file])
|
||||||
|
allow = @allow_unconfigured
|
||||||
|
|
||||||
|
File.read(policy_file).each_line do |line|
|
||||||
|
next if line =~ /^(#.*|\s*)$/
|
||||||
|
|
||||||
|
if line =~ /^policy\s+default\s+(\w+)/
|
||||||
|
if $1 == 'allow'
|
||||||
|
allow = true
|
||||||
|
else
|
||||||
|
allow = false
|
||||||
|
end
|
||||||
|
elsif line =~ /^(allow|deny)\t+(.+?)\t+(.+?)\t+(.+?)(\t+(.+?))*$/
|
||||||
|
if check_policy($2, $3, $4, $6)
|
||||||
|
if $1 == 'allow'
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
deny("Denying based on explicit 'deny' policy rule in policyfile: %s" % File.basename(policy_file))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Log.debug("Cannot parse policy line: %s" % line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
allow || deny("Denying based on default policy in %s" % File.basename(policy_file))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if a request made by a caller matches the state defined in the policy
|
||||||
|
def check_policy(rpccaller, actions, facts, classes)
|
||||||
|
# If we have a wildcard caller or the caller matches our policy line
|
||||||
|
# then continue else skip this policy line\
|
||||||
|
if (rpccaller != '*') && (rpccaller != @caller)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we have a wildcard actions list or the request action is in the list
|
||||||
|
# of actions in the policy line continue, else skip this policy line
|
||||||
|
if (actions != '*') && !(actions.split.include?(@action))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
unless classes
|
||||||
|
return parse_compound(facts)
|
||||||
|
else
|
||||||
|
return parse_facts(facts) && parse_classes(classes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_facts(facts)
|
||||||
|
return true if facts == '*'
|
||||||
|
|
||||||
|
if is_compound?(facts)
|
||||||
|
return parse_compound(facts)
|
||||||
|
else
|
||||||
|
facts.split.each do |fact|
|
||||||
|
return false unless lookup_fact(fact)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_classes(classes)
|
||||||
|
return true if classes == '*'
|
||||||
|
|
||||||
|
if is_compound?(classes)
|
||||||
|
return parse_compound(classes)
|
||||||
|
else
|
||||||
|
classes.split.each do |klass|
|
||||||
|
return false unless lookup_class(klass)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_fact(fact)
|
||||||
|
if fact =~ /(.+)(<|>|=|<=|>=)(.+)/
|
||||||
|
lv = $1
|
||||||
|
sym = $2
|
||||||
|
rv = $3
|
||||||
|
|
||||||
|
sym = '==' if sym == '='
|
||||||
|
return eval("'#{Util.get_fact(lv)}'#{sym}'#{rv}'")
|
||||||
|
else
|
||||||
|
Log.warn("Class found where fact was expected")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_class(klass)
|
||||||
|
if klass =~ /(.+)(<|>|=|<=|>=)(.+)/
|
||||||
|
Log.warn("Fact found where class was expected")
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return Util.has_cf_class?(klass)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup(token)
|
||||||
|
if token =~ /(.+)(<|>|=|<=|>=)(.+)/
|
||||||
|
return lookup_fact(token)
|
||||||
|
else
|
||||||
|
return lookup_class(token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Here we lookup the full path of the policy file. If the policyfile
|
||||||
|
# does not exist, we check to see if a default file was set and
|
||||||
|
# determine its full path. If no default file exists, or default was
|
||||||
|
# not specified, we return false.
|
||||||
|
def lookup_policy_file
|
||||||
|
policy_file = File.join(@configdir, "policies", "#{@agent}.policy")
|
||||||
|
|
||||||
|
Log.debug("Looking for policy in #{policy_file}")
|
||||||
|
|
||||||
|
return policy_file if File.exist?(policy_file)
|
||||||
|
|
||||||
|
if @config.pluginconf.fetch('actionpolicy.enable_default', 'n') =~ /^1|y/i
|
||||||
|
defaultname = @config.pluginconf.fetch('actionpolicy.default_name', 'default')
|
||||||
|
default_file = File.join(@configdir, "policies", "#{defaultname}.policy")
|
||||||
|
|
||||||
|
Log.debug("Initial lookup failed: looking for policy in #{default_file}")
|
||||||
|
|
||||||
|
return default_file if File.exist?(default_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.debug('Could not find any policy files.')
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Evalute a compound statement and return its truth value
|
||||||
|
def eval_statement(statement)
|
||||||
|
token_type = statement.keys.first
|
||||||
|
token_value = statement.values.first
|
||||||
|
|
||||||
|
return token_value if (token_type != 'statement' && token_type != 'fstatement')
|
||||||
|
|
||||||
|
if token_type == 'statement'
|
||||||
|
return lookup(token_value)
|
||||||
|
elsif token_type == 'fstatement'
|
||||||
|
begin
|
||||||
|
return Matcher.eval_compound_fstatement(token_value)
|
||||||
|
rescue => e
|
||||||
|
Log.warn("Could not call Data function in policy file: #{e}")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_compound?(list)
|
||||||
|
list.split.each do |token|
|
||||||
|
if token =~ /^!|^not$|^or$|^and$|\(.+\)/
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_compound(list)
|
||||||
|
stack = Matcher.create_compound_callstack(list)
|
||||||
|
|
||||||
|
begin
|
||||||
|
stack.map!{ |item| eval_statement(item) }
|
||||||
|
rescue => e
|
||||||
|
Log.debug(e.to_s)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
eval(stack.join(' '))
|
||||||
|
end
|
||||||
|
|
||||||
|
def deny(logline)
|
||||||
|
Log.debug(logline)
|
||||||
|
|
||||||
|
raise(RPCAborted, 'You are not authorized to call this agent or action.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user