jrun-merb_doorman 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README +29 -0
- data/Rakefile +67 -0
- data/TODO +3 -0
- data/lib/merb_doorman.rb +118 -0
- data/lib/merb_doorman/helpers.rb +22 -0
- data/lib/merb_doorman/merbtasks.rb +6 -0
- data/lib/merb_doorman/rule.rb +61 -0
- data/spec/allow_all_spec.rb +16 -0
- data/spec/class_methods_spec.rb +46 -0
- data/spec/deny_all_spec.rb +12 -0
- data/spec/fixtures/controllers/exceptions.rb +5 -0
- data/spec/fixtures/controllers/test_controller.rb +17 -0
- data/spec/fixtures/controllers/test_helpers.rb +5 -0
- data/spec/fixtures/model/user.rb +14 -0
- data/spec/fixtures/views/test_helpers/allow_via_role.html.erb +3 -0
- data/spec/fixtures/views/test_helpers/deny_via_role.html.erb +3 -0
- data/spec/fixtures/views/test_helpers/example_setup_is_ok.html.erb +1 -0
- data/spec/host_spec.rb +49 -0
- data/spec/matchers/authorized_matcher.rb +19 -0
- data/spec/matchers/unauthorized_matcher.rb +19 -0
- data/spec/merb_doorman/helpers_spec.rb +43 -0
- data/spec/merb_doorman/rule_spec.rb +89 -0
- data/spec/role_spec.rb +81 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/user_agent_spec.rb +19 -0
- data/spec/user_spec.rb +73 -0
- metadata +97 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Michael D. Ivey <ivey@gweezlebur.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
merb_doorman
|
2
|
+
============
|
3
|
+
|
4
|
+
*** Want to contribute? Some of the code isn't spec'd yet. ***
|
5
|
+
|
6
|
+
|
7
|
+
Merb plugin that provides an allow/deny DSL for controlling access
|
8
|
+
|
9
|
+
|
10
|
+
# mostly open:
|
11
|
+
# there is an implicit "allow :all" as the last rule
|
12
|
+
# rules continue to match until an allow is found, or we run out
|
13
|
+
# of rules
|
14
|
+
|
15
|
+
deny :host => "209.34.*"
|
16
|
+
deny :user => "bill" # calls current_user.login, but this is configurable
|
17
|
+
deny :user_agent => /MSIE/
|
18
|
+
deny {|c| c.params["arbitrary"] == "expressions"}
|
19
|
+
|
20
|
+
|
21
|
+
# mostly closed:
|
22
|
+
deny :all # removes implicit final allow :all
|
23
|
+
allow :host => "*.example.com"
|
24
|
+
allow :time => "8am-5pm" # not implemented yet
|
25
|
+
|
26
|
+
# store a block for repeated usage
|
27
|
+
Merb::Access.add_block :admin, {|c| c.current_user.admin?}
|
28
|
+
|
29
|
+
allow :admin
|
data/Rakefile
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
require 'merb-core'
|
6
|
+
require 'merb-core/tasks/merb'
|
7
|
+
|
8
|
+
GEM_NAME = "merb_doorman"
|
9
|
+
GEM_VERSION = "0.0.1"
|
10
|
+
AUTHOR = "Michael D. Ivey"
|
11
|
+
EMAIL = "ivey@gweezlebur.com"
|
12
|
+
HOMEPAGE = "http://github.com/ivey/merb_doorman/"
|
13
|
+
SUMMARY = "Merb plugin that provides an allow/deny DSL for controlling access"
|
14
|
+
|
15
|
+
spec = Gem::Specification.new do |s|
|
16
|
+
s.rubyforge_project = 'merb'
|
17
|
+
s.name = GEM_NAME
|
18
|
+
s.version = GEM_VERSION
|
19
|
+
s.platform = Gem::Platform::RUBY
|
20
|
+
s.has_rdoc = true
|
21
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
22
|
+
s.summary = SUMMARY
|
23
|
+
s.description = s.summary
|
24
|
+
s.author = AUTHOR
|
25
|
+
s.email = EMAIL
|
26
|
+
s.homepage = HOMEPAGE
|
27
|
+
s.add_dependency('merb-core', '>= 1.0')
|
28
|
+
s.require_path = 'lib'
|
29
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
33
|
+
pkg.gem_spec = spec
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "install the plugin as a gem"
|
37
|
+
task :install do
|
38
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Uninstall the gem"
|
42
|
+
task :uninstall do
|
43
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
44
|
+
end
|
45
|
+
|
46
|
+
namespace :gem do
|
47
|
+
desc "Repackage, uninstall and install gem"
|
48
|
+
task :refresh do
|
49
|
+
Rake::Task[:spec].invoke
|
50
|
+
Rake::Task[:repackage].invoke
|
51
|
+
Rake::Task[:uninstall].invoke
|
52
|
+
Rake::Task[:install].invoke
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Create a gemspec file"
|
57
|
+
task :gemspec do
|
58
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
59
|
+
file.puts spec.to_ruby
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Run the examples"
|
64
|
+
Spec::Rake::SpecTask.new do |t|
|
65
|
+
t.spec_files = ["spec/**/*_spec.rb"]
|
66
|
+
t.spec_opts = %w[--color --format specdoc --diff]
|
67
|
+
end
|
data/TODO
ADDED
data/lib/merb_doorman.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'merb_doorman/rule'
|
2
|
+
require 'merb_doorman/helpers'
|
3
|
+
|
4
|
+
module Merb
|
5
|
+
module Plugins
|
6
|
+
module Doorman
|
7
|
+
SUPPORTED_METHODS = [:block, :host, :role, :time, :user, :user_agent]
|
8
|
+
|
9
|
+
def self.supported_method?(method)
|
10
|
+
SUPPORTED_METHODS.include?(method)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
base.send(:include, InstanceMethods)
|
16
|
+
base.class_eval do
|
17
|
+
before :_doorman_check_acl
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def _doorman_list
|
23
|
+
@_doorman_list ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
def _doorman_default
|
27
|
+
@_doorman_default ||= :allow
|
28
|
+
end
|
29
|
+
|
30
|
+
def deny(*args, &block)
|
31
|
+
_add_acl(:deny, args, block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def allow(*args, &block)
|
35
|
+
_add_acl(:allow, args, block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def _clear_acl_list
|
39
|
+
@_doorman_list = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def _add_acl(type, args, block)
|
43
|
+
opts = args.is_a?(Array) ? args.first : args
|
44
|
+
if opts == :all
|
45
|
+
@_doorman_default = type
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
if block
|
49
|
+
_doorman_list << Rule.from_block(type, opts, &block)
|
50
|
+
else
|
51
|
+
_doorman_list << Rule.from_hash(type, opts)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def _doorman_user_block
|
56
|
+
Merb::Plugins.config[:merb_doorman][:user_block]
|
57
|
+
end
|
58
|
+
|
59
|
+
def _doorman_role_block
|
60
|
+
Merb::Plugins.config[:merb_doorman][:role_block]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module InstanceMethods
|
65
|
+
private
|
66
|
+
def _doorman_check_acl
|
67
|
+
allowed = false
|
68
|
+
self.class._doorman_list.each do |rule|
|
69
|
+
next unless rule.evaluate?(self.action_name)
|
70
|
+
if _check_rule(rule)
|
71
|
+
allowed = true
|
72
|
+
else
|
73
|
+
raise ::Merb::ControllerExceptions::Unauthorized
|
74
|
+
end
|
75
|
+
end
|
76
|
+
if self.class._doorman_default == :deny && !allowed
|
77
|
+
raise ::Merb::ControllerExceptions::Unauthorized
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def _check_rule(rule)
|
82
|
+
match = case rule.method
|
83
|
+
when :block
|
84
|
+
rule.value.call(self)
|
85
|
+
when :host
|
86
|
+
request.host =~ Regexp.new(rule.value)
|
87
|
+
when :role
|
88
|
+
self.class._doorman_role_block.call(self, rule.value)
|
89
|
+
when :time
|
90
|
+
false #not implemented
|
91
|
+
when :user
|
92
|
+
self.class._doorman_user_block.call(self, rule.value)
|
93
|
+
when :user_agent
|
94
|
+
request.user_agent =~ Regexp.new(rule.value)
|
95
|
+
else
|
96
|
+
false
|
97
|
+
end
|
98
|
+
rule.deny? ? !match : match
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if defined?(Merb::Plugins)
|
107
|
+
Merb::Plugins.config[:merb_doorman][:user_block] = proc do |c, login|
|
108
|
+
c.respond_to?(:current_user) &&
|
109
|
+
c.current_user.login == login.to_s
|
110
|
+
end
|
111
|
+
Merb::Plugins.config[:merb_doorman][:role_block] = proc do |c, role|
|
112
|
+
c.respond_to?(:current_user) &&
|
113
|
+
c.current_user.respond_to?(:has_role?) &&
|
114
|
+
c.current_user.has_role?(role)
|
115
|
+
end
|
116
|
+
Merb::Controller.send(:include, Merb::Plugins::Doorman)
|
117
|
+
Merb::GlobalHelpers.send(:include, Merb::Plugins::Doorman::Helpers)
|
118
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Merb
|
2
|
+
module Plugins
|
3
|
+
module Doorman
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
def allow(*args, &blk)
|
7
|
+
_capture_within_rule_context(:allow, args, &blk)
|
8
|
+
end
|
9
|
+
|
10
|
+
def deny(*args, &blk)
|
11
|
+
_capture_within_rule_context(:deny, args, &blk)
|
12
|
+
end
|
13
|
+
|
14
|
+
def _capture_within_rule_context(type, args, &blk)
|
15
|
+
_check_rule(Doorman::Rule.from_hash(type, args.first)) ? capture(&blk) : ""
|
16
|
+
end
|
17
|
+
private :_capture_within_rule_context
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Merb
|
2
|
+
module Plugins
|
3
|
+
module Doorman
|
4
|
+
class InvalidRule < StandardError
|
5
|
+
def initialize(*args)
|
6
|
+
super "merb_doorman: invalid rule #{args.inspect}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Rule
|
11
|
+
def self.from_hash(type, opts)
|
12
|
+
rule = new(type)
|
13
|
+
|
14
|
+
h = opts.except(:only, :exclude)
|
15
|
+
raise InvalidRule.new(type, opts) if h.size > 1
|
16
|
+
|
17
|
+
rule.method = h.keys.first.to_sym
|
18
|
+
unless Doorman.supported_method?(rule.method)
|
19
|
+
raise InvalidRule.new(type, opts)
|
20
|
+
end
|
21
|
+
rule.value = h.values.first
|
22
|
+
rule.limits = extract_limits(opts)
|
23
|
+
rule
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_block(type, opts = nil, &block)
|
27
|
+
raise InvalidRule.new(type, opts, block) unless block.arity == 1
|
28
|
+
opts ||= {}
|
29
|
+
|
30
|
+
rule = new(type)
|
31
|
+
rule.method = :block
|
32
|
+
rule.value = block
|
33
|
+
rule.limits = extract_limits(opts)
|
34
|
+
rule
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.extract_limits(h)
|
38
|
+
h.only(:only, :exclude).inject({}) do |limits, kv|
|
39
|
+
limits.merge!(kv.first => Array(kv.last))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :type, :method, :value, :limits
|
44
|
+
|
45
|
+
def initialize(type)
|
46
|
+
@type, @limits = type, {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def deny?
|
50
|
+
type == :deny
|
51
|
+
end
|
52
|
+
|
53
|
+
def evaluate?(action_name)
|
54
|
+
(!limits.key?(:only) || limits[:only].include?(action_name)) &&
|
55
|
+
(!limits.key?(:exclude) || !limits[:exclude].include?(action_name))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class AllowAllByDefault < Merb::Plugins::Doorman::TestController; end
|
4
|
+
|
5
|
+
class ExplicitAllowAll < Merb::Plugins::Doorman::TestController
|
6
|
+
allow :all
|
7
|
+
end
|
8
|
+
|
9
|
+
[AllowAllByDefault, ExplicitAllowAll].each do |controller|
|
10
|
+
describe controller, 'request' do
|
11
|
+
it "should be authorized when explicitly denying all" do
|
12
|
+
response = request(url(:controller => controller.controller_name))
|
13
|
+
response.should be_authorized
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Merb::Plugins::Doorman::ClassMethods do
|
4
|
+
include Merb::Plugins::Doorman::ClassMethods
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
_clear_acl_list
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should store the ACL" do
|
11
|
+
_doorman_list.should be_an_instance_of(Array)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have a deny method" do
|
15
|
+
lambda { deny :all }.should_not raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should store a deny entry on the ACL" do
|
19
|
+
s = _doorman_list.size
|
20
|
+
deny :host => "192.168.*"
|
21
|
+
_doorman_list.should have(s + 1).elements
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have an allow method" do
|
25
|
+
lambda { allow :all }.should_not raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should store an allow entry on the ACL" do
|
29
|
+
s = _doorman_list.size
|
30
|
+
allow :user_agent => /MSIE/
|
31
|
+
_doorman_list.should have(s + 1).elements
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should allow valid ACL entries" do
|
35
|
+
lambda { deny :host => "192.168.*" }.should_not raise_error
|
36
|
+
lambda { deny :user => "bill" }.should_not raise_error
|
37
|
+
lambda { deny :user_agent => /MSIE/ }.should_not raise_error
|
38
|
+
lambda { deny :time => "8am-5pm" }.should_not raise_error
|
39
|
+
lambda { deny {|c| c.foo } }.should_not raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should reject invalid ACL entries" do
|
43
|
+
lambda { deny :foo => "3" }.should raise_error
|
44
|
+
lambda { deny { foo } }.should raise_error
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class DenyAll < Merb::Plugins::Doorman::TestController
|
4
|
+
deny :all
|
5
|
+
end
|
6
|
+
|
7
|
+
describe DenyAll, 'request' do
|
8
|
+
it "should be unauthorized when explicitly denying all" do
|
9
|
+
response = request(url(:controller => DenyAll))
|
10
|
+
response.should be_unauthorized
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Merb
|
2
|
+
module Plugins
|
3
|
+
module Doorman
|
4
|
+
|
5
|
+
class TestController < Merb::Controller
|
6
|
+
self._template_root = File.dirname(__FILE__) / ".." / "views"
|
7
|
+
|
8
|
+
class_inheritable_accessor :current_user
|
9
|
+
self.current_user ||= User.new
|
10
|
+
|
11
|
+
def show; render('Allowed Access') end
|
12
|
+
def index; render('Allowed Access') end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Good to go</h1>
|
data/spec/host_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class AccessControllByHost < Merb::Plugins::Doorman::TestController
|
4
|
+
allow :all
|
5
|
+
allow :host => "allowed.example.org"
|
6
|
+
deny :host => "denied.example.org"
|
7
|
+
end
|
8
|
+
describe AccessControllByHost, 'request' do
|
9
|
+
it "should be unauthorized when the host is not explicitly allowed" do
|
10
|
+
response = request(url(:controller => AccessControllByHost))
|
11
|
+
response.should be_unauthorized
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be authorized when the host is allowed" do
|
15
|
+
response = request(url(:controller => AccessControllByHost), 'SERVER_NAME' => 'allowed.example.org')
|
16
|
+
response.should be_authorized
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be unauthorized when the host is denied" do
|
20
|
+
response = request(url(:controller => AccessControllByHost), 'SERVER_NAME' => 'denied.example.org')
|
21
|
+
response.should be_unauthorized
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AccessControllDenyAllExceptHost < Merb::Plugins::Doorman::TestController
|
26
|
+
deny :all
|
27
|
+
allow :host => "allowed.example.org"
|
28
|
+
end
|
29
|
+
describe AccessControllDenyAllExceptHost, 'request' do
|
30
|
+
it "should be authorized when the host is allowed" do
|
31
|
+
response = request(url(:controller => AccessControllDenyAllExceptHost))
|
32
|
+
response.should be_unauthorized
|
33
|
+
response = request(url(:controller => AccessControllDenyAllExceptHost), 'SERVER_NAME' => 'allowed.example.org')
|
34
|
+
response.should be_authorized
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class AccessControllAllowAllExceptHost < Merb::Plugins::Doorman::TestController
|
39
|
+
allow :all
|
40
|
+
deny :host => "denied.example.org"
|
41
|
+
end
|
42
|
+
describe AccessControllDenyAllExceptHost, 'request' do
|
43
|
+
it "should be authorized when the host is allowed" do
|
44
|
+
response = request(url(:controller => AccessControllAllowAllExceptHost))
|
45
|
+
response.should be_authorized
|
46
|
+
response = request(url(:controller => AccessControllAllowAllExceptHost), 'SERVER_NAME' => 'denied.example.org')
|
47
|
+
response.should be_unauthorized
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class BeAuthorized
|
2
|
+
def matches?(response)
|
3
|
+
@response = response
|
4
|
+
response.status == 200 && response.body == 'Allowed Access'
|
5
|
+
end
|
6
|
+
|
7
|
+
# ==== Returns
|
8
|
+
# String:: The failure message.
|
9
|
+
def failure_message
|
10
|
+
"expected the response from #{@response.url} #{$/} to have the status 200 Ok and body 'Allowed Access'" <<
|
11
|
+
"#{$/} but the status is #{@response.status} and the body is '#{@response.body}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
# ==== Returns
|
15
|
+
# String:: The failure message to be displayed in negative matches.
|
16
|
+
def negative_failure_message
|
17
|
+
"expected the response from #{@response.url} #{$/} not to have the status 200 Ok and body 'Allowed Access'"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class BeUnauthorized
|
2
|
+
def matches?(response)
|
3
|
+
@response = response
|
4
|
+
response.status == 401 && response.body == 'Unauthorized'
|
5
|
+
end
|
6
|
+
|
7
|
+
# ==== Returns
|
8
|
+
# String:: The failure message.
|
9
|
+
def failure_message
|
10
|
+
"expected the response from #{@response.url} #{$/} to have the status 401 Unauthorized and body 'Unauthorized'" <<
|
11
|
+
"#{$/} but the status is #{@response.status} and the body is '#{@response.body}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
# ==== Returns
|
15
|
+
# String:: The failure message to be displayed in negative matches.
|
16
|
+
def negative_failure_message
|
17
|
+
"expected the response from #{@response.url} #{$/} not to have the status 200 Ok and body 'Allowed Access'"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Merb::Plugins::Doorman::Helpers do
|
4
|
+
it "examples should be setup successfully" do
|
5
|
+
response = request(url(:controller => TestHelpers, :action => :example_setup_is_ok))
|
6
|
+
response.body.should == "<h1>Good to go</h1>"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#allow" do
|
10
|
+
before(:each) do
|
11
|
+
TestHelpers.current_user.reset
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should render the result of the block when allowed" do
|
15
|
+
TestHelpers.current_user.roles << :admin
|
16
|
+
response = request(url(:controller => TestHelpers, :action => :allow_via_role))
|
17
|
+
response.body.should == "\n <h1>Allowed</h1>\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not return the result of the block when not allowed" do
|
21
|
+
response = request(url(:controller => TestHelpers, :action => :allow_via_role))
|
22
|
+
response.body.should == ""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#deny" do
|
27
|
+
before(:each) do
|
28
|
+
TestHelpers.current_user.reset
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not return the result of the block when denied" do
|
32
|
+
TestHelpers.current_user.roles << :troll
|
33
|
+
response = request(url(:controller => TestHelpers, :action => :deny_via_role))
|
34
|
+
response.body.should == ""
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should render the result of the block when not denied" do
|
38
|
+
TestHelpers.current_user.roles << :anything_but_troll
|
39
|
+
response = request(url(:controller => TestHelpers, :action => :deny_via_role))
|
40
|
+
response.body.should == "\n <h1>Allowed</h1>\n"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.dirname(__FILE__) / '../spec_helper'
|
2
|
+
|
3
|
+
Rule = Merb::Plugins::Doorman::Rule
|
4
|
+
|
5
|
+
describe Merb::Plugins::Doorman::Rule do
|
6
|
+
describe '.from_hash' do
|
7
|
+
it "should set the method using the opts first key" do
|
8
|
+
rule = Rule.from_hash(:allow, :role => :whatever)
|
9
|
+
rule.method.should == :role
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should set the value using the opts first value" do
|
13
|
+
rule = Rule.from_hash(:allow, :role => :whatever)
|
14
|
+
rule.value.should == :whatever
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should move :only from options to limits" do
|
18
|
+
rule = Rule.from_hash(:allow, :role => :whatever, :only => :index)
|
19
|
+
rule.limits[:only].should_not be_nil
|
20
|
+
rule.limits[:role].should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should move :exclude from options to limits" do
|
24
|
+
rule = Rule.from_hash(:allow, :role => :whatever, :exclude => :index)
|
25
|
+
rule.limits[:exclude].should_not be_nil
|
26
|
+
rule.limits[:role].should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should convert a single :only value to an Array" do
|
30
|
+
rule = Rule.from_hash(:allow, :role => :admin, :only => :index)
|
31
|
+
rule.limits[:only].should == [:index]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should convert a single :exclude value to an Array" do
|
35
|
+
rule = Rule.from_hash(:allow, :role => :admin, :exclude => :index)
|
36
|
+
rule.limits[:exclude].should == [:index]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise an InvalidRule error when the options hash as too many values" do
|
40
|
+
lambda do
|
41
|
+
Rule.from_hash(:allow, :role => :admin, :user => :bob)
|
42
|
+
end.should raise_error(Merb::Plugins::Doorman::InvalidRule)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise an InvalidRule error when the method isn't supported" do
|
46
|
+
lambda do
|
47
|
+
Rule.from_hash(:allow, :not_suppoted => :whatever)
|
48
|
+
end.should raise_error(Merb::Plugins::Doorman::InvalidRule)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '.from_block' do
|
53
|
+
def block
|
54
|
+
proc {|ignored| true }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should set the method to :block" do
|
58
|
+
rule = Rule.from_block(:allow, &block)
|
59
|
+
rule.method.should == :block
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should set the value to the block" do
|
63
|
+
rule = Rule.from_block(:allow, &block)
|
64
|
+
rule.value.should be_instance_of(Proc)
|
65
|
+
rule.value.call(:ignored).should be_true
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should move :only from options to limits" do
|
69
|
+
rule = Rule.from_block(:allow, :only => :index, &block)
|
70
|
+
rule.limits[:only].should == [:index]
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should move :exclude from options to limits" do
|
74
|
+
rule = Rule.from_block(:allow, :exclude => [:index, :show], &block)
|
75
|
+
rule.limits[:exclude].should == [:index, :show]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should convert a single :only value to an Array" do
|
79
|
+
rule = Rule.from_block(:allow, :exclude => :index, &block)
|
80
|
+
rule.limits[:exclude].should == [:index]
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should raise an InvalidRule error when the blocks arity is not one" do
|
84
|
+
lambda do
|
85
|
+
Rule.from_block(:allow) {}
|
86
|
+
end.should raise_error(Merb::Plugins::Doorman::InvalidRule)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/role_spec.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class AllowedRole < Merb::Plugins::Doorman::TestController
|
4
|
+
allow :role => :admin
|
5
|
+
end
|
6
|
+
describe AllowedRole, 'request' do
|
7
|
+
before(:each) do
|
8
|
+
AllowedRole.current_user.reset
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be authorized when the current user belongs to the allowed role" do
|
12
|
+
AllowedRole.current_user.roles << :admin
|
13
|
+
response = request(url(:controller => AllowedRole))
|
14
|
+
response.should be_authorized
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be unauthorized when the current user does not belong to the allowed role" do
|
18
|
+
response = request(url(:controller => AllowedRole))
|
19
|
+
response.should be_unauthorized
|
20
|
+
|
21
|
+
AllowedRole.current_user.roles << :not_admin
|
22
|
+
response = request(url(:controller => AllowedRole))
|
23
|
+
response.should be_unauthorized
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class DeniedRole < Merb::Plugins::Doorman::TestController
|
28
|
+
deny :role => :troll
|
29
|
+
end
|
30
|
+
describe DeniedRole, 'request' do
|
31
|
+
before(:each) do
|
32
|
+
DeniedRole.current_user.reset
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be authorized when the current user does not belong to the denied role" do
|
36
|
+
response = request(url(:controller => DeniedRole))
|
37
|
+
response.should be_authorized
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be unauthorized when the current user belongs to the denied role" do
|
41
|
+
DeniedRole.current_user.roles << :troll
|
42
|
+
response = request(url(:controller => DeniedRole))
|
43
|
+
response.should be_unauthorized
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class AllowedAndDeniedRoles < Merb::Plugins::Doorman::TestController
|
48
|
+
allow :role => :admin
|
49
|
+
deny :role => :troll
|
50
|
+
end
|
51
|
+
describe AllowedAndDeniedRoles, 'request' do
|
52
|
+
before(:each) do
|
53
|
+
AllowedAndDeniedRoles.current_user.reset
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should be authorized when the current user belongs to the allowed role" do
|
57
|
+
AllowedAndDeniedRoles.current_user.roles << :admin
|
58
|
+
response = request(url(:controller => AllowedAndDeniedRoles))
|
59
|
+
response.should be_authorized
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be unauthorized when the current user belongs to the denied role" do
|
63
|
+
AllowedAndDeniedRoles.current_user.roles << :troll
|
64
|
+
response = request(url(:controller => AllowedAndDeniedRoles))
|
65
|
+
response.should be_unauthorized
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class AllowedOnly < Merb::Plugins::Doorman::TestController
|
70
|
+
allow :role => :admin, :only => :show
|
71
|
+
end
|
72
|
+
describe AllowedAndDeniedRoles, 'request' do
|
73
|
+
before(:each) do
|
74
|
+
AllowedOnly.current_user.reset
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should be authorized when the rule does not apply to the action" do
|
78
|
+
response = request(url(:controller => AllowedOnly, :action => :index))
|
79
|
+
response.should be_authorized
|
80
|
+
end
|
81
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
$TESTING=true
|
2
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
require 'rubygems'
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb_doorman'
|
6
|
+
|
7
|
+
this_dir = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
Merb.push_path(:matchers, this_dir / "matchers")
|
10
|
+
Merb.push_path(:fixture_model, this_dir / "fixtures/model")
|
11
|
+
Merb.push_path(:fixture_controller, this_dir / "fixtures/controllers")
|
12
|
+
|
13
|
+
Merb.start :environment => 'test', :adapter => 'runner'
|
14
|
+
|
15
|
+
def be_authorized
|
16
|
+
BeAuthorized.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def be_unauthorized
|
20
|
+
BeUnauthorized.new
|
21
|
+
end
|
22
|
+
|
23
|
+
Spec::Runner.configure do |config|
|
24
|
+
config.include Merb::Test::RequestHelper
|
25
|
+
config.before(:all) do
|
26
|
+
Merb::Router.prepare { default_routes }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# # deny :user => "bill" # calls current_user.login, but this is configurable
|
31
|
+
# # deny {|c| c.params["arbitrary"] == "expressions"}
|
32
|
+
# #
|
33
|
+
# #
|
34
|
+
# # # mostly closed:
|
35
|
+
# # deny :all # removes implicit final allow :all
|
36
|
+
# # allow :host => "*.example.com"
|
37
|
+
# # allow :time => "8am-5pm"
|
38
|
+
# #
|
39
|
+
# # # store a block for repeated usage
|
40
|
+
# # Merb::Access.add_block :admin, {|c| c.current_user.admin?}
|
41
|
+
# #
|
42
|
+
# # allow :admin
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class AccessControllByUserAgent < Merb::Plugins::Doorman::TestController
|
4
|
+
deny :user_agent => /MSIE/
|
5
|
+
allow :user_agent => /iPhone/
|
6
|
+
end
|
7
|
+
|
8
|
+
describe AccessControllByUserAgent, 'request' do
|
9
|
+
it "should be authorized when the user agent is allowed" do
|
10
|
+
response = request(url(:controller => AccessControllByUserAgent.controller_name), 'HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5F136 Safari/525.20')
|
11
|
+
response.should be_authorized
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be unauthorized when the user agent is denied" do
|
15
|
+
response = request(url(:controller => AccessControllByUserAgent.controller_name), 'HTTP_USER_AGENT' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90; MSIECrawler)')
|
16
|
+
response.should be_unauthorized
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
data/spec/user_spec.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class AllowedUser < Merb::Plugins::Doorman::TestController
|
4
|
+
allow :user => :nancy
|
5
|
+
end
|
6
|
+
describe AllowedUser, 'request' do
|
7
|
+
before(:each) do
|
8
|
+
AllowedUser.current_user.reset
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be authorized when the current user has an allowed login" do
|
12
|
+
AllowedUser.current_user.login = 'nancy'
|
13
|
+
response = request(url(:controller => AllowedUser))
|
14
|
+
response.should be_authorized
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be unauthorized when the current user does not have an allowed login" do
|
18
|
+
response = request(url(:controller => AllowedUser))
|
19
|
+
response.should be_unauthorized
|
20
|
+
|
21
|
+
AllowedUser.current_user.login = 'jackie boy'
|
22
|
+
response = request(url(:controller => AllowedUser))
|
23
|
+
response.should be_unauthorized
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class DeniedUser < Merb::Plugins::Doorman::TestController
|
28
|
+
deny :user => 'roark'
|
29
|
+
deny :user => 'kevin'
|
30
|
+
end
|
31
|
+
describe DeniedUser, 'request' do
|
32
|
+
before(:each) do
|
33
|
+
DeniedUser.current_user.reset
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be authorized when the current user does not have a denied login" do
|
37
|
+
DeniedUser.current_user.login = 'nancy'
|
38
|
+
response = request(url(:controller => DeniedUser))
|
39
|
+
response.should be_authorized
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should be unauthorized when the current user has a denied login" do
|
43
|
+
DeniedUser.current_user.login = 'roark'
|
44
|
+
response = request(url(:controller => DeniedUser))
|
45
|
+
response.should be_unauthorized
|
46
|
+
|
47
|
+
DeniedUser.current_user.login = 'kevin'
|
48
|
+
response = request(url(:controller => DeniedUser))
|
49
|
+
response.should be_unauthorized
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class AllowedAndDeniedUsers < Merb::Plugins::Doorman::TestController
|
54
|
+
allow :role => :admin
|
55
|
+
deny :role => :troll
|
56
|
+
end
|
57
|
+
describe AllowedAndDeniedUsers, 'request' do
|
58
|
+
before(:each) do
|
59
|
+
AllowedAndDeniedUsers.current_user.reset
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be authorized when the current user belongs to the allowed role" do
|
63
|
+
AllowedAndDeniedUsers.current_user.roles << :admin
|
64
|
+
response = request(url(:controller => AllowedAndDeniedUsers))
|
65
|
+
response.should be_authorized
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be unauthorized when the current user belongs to the denied role" do
|
69
|
+
AllowedAndDeniedUsers.current_user.roles << :troll
|
70
|
+
response = request(url(:controller => AllowedAndDeniedUsers))
|
71
|
+
response.should be_unauthorized
|
72
|
+
end
|
73
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jrun-merb_doorman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael D. Ivey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-16 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: merb-core
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "1.0"
|
23
|
+
version:
|
24
|
+
description: Merb plugin that provides an allow/deny DSL for controlling access
|
25
|
+
email: ivey@gweezlebur.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README
|
32
|
+
- LICENSE
|
33
|
+
- TODO
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README
|
37
|
+
- Rakefile
|
38
|
+
- TODO
|
39
|
+
- lib/merb_doorman
|
40
|
+
- lib/merb_doorman/helpers.rb
|
41
|
+
- lib/merb_doorman/merbtasks.rb
|
42
|
+
- lib/merb_doorman/rule.rb
|
43
|
+
- lib/merb_doorman.rb
|
44
|
+
- spec/allow_all_spec.rb
|
45
|
+
- spec/class_methods_spec.rb
|
46
|
+
- spec/deny_all_spec.rb
|
47
|
+
- spec/fixtures
|
48
|
+
- spec/fixtures/controllers
|
49
|
+
- spec/fixtures/controllers/exceptions.rb
|
50
|
+
- spec/fixtures/controllers/test_controller.rb
|
51
|
+
- spec/fixtures/controllers/test_helpers.rb
|
52
|
+
- spec/fixtures/model
|
53
|
+
- spec/fixtures/model/user.rb
|
54
|
+
- spec/fixtures/views
|
55
|
+
- spec/fixtures/views/test_helpers
|
56
|
+
- spec/fixtures/views/test_helpers/allow_via_role.html.erb
|
57
|
+
- spec/fixtures/views/test_helpers/deny_via_role.html.erb
|
58
|
+
- spec/fixtures/views/test_helpers/example_setup_is_ok.html.erb
|
59
|
+
- spec/host_spec.rb
|
60
|
+
- spec/matchers
|
61
|
+
- spec/matchers/authorized_matcher.rb
|
62
|
+
- spec/matchers/unauthorized_matcher.rb
|
63
|
+
- spec/merb_doorman
|
64
|
+
- spec/merb_doorman/helpers_spec.rb
|
65
|
+
- spec/merb_doorman/rule_spec.rb
|
66
|
+
- spec/role_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- spec/user_agent_spec.rb
|
69
|
+
- spec/user_spec.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/ivey/merb_doorman/
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: "0"
|
88
|
+
version:
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project: merb
|
92
|
+
rubygems_version: 1.2.0
|
93
|
+
signing_key:
|
94
|
+
specification_version: 2
|
95
|
+
summary: Merb plugin that provides an allow/deny DSL for controlling access
|
96
|
+
test_files: []
|
97
|
+
|