ixtlan-guard 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,15 +8,7 @@ module Guard
8
8
  # check_class_collision :suffix => "Guard"
9
9
 
10
10
  def create_guard_file
11
- template 'guard.rb', File.join('app', 'guards', class_path, "#{file_name}_guard.rb")
11
+ template 'guard.yml', File.join('app', 'guards', class_path, "#{file_name}_guard.yml")
12
12
  end
13
-
14
- def guard_class_name
15
- class_name
16
- end
17
-
18
- def aliases
19
- end
20
-
21
13
  end
22
14
  end
@@ -3,24 +3,17 @@ module Guard
3
3
  class ScaffoldGenerator < Rails::Generators::NamedBase
4
4
  include Rails::Generators::ResourceHelpers
5
5
 
6
- source_root File.expand_path('../templates', __FILE__)
6
+ source_root File.expand_path('../../templates', __FILE__)
7
7
 
8
8
  # check_class_collision :suffix => "Guard"
9
9
 
10
10
  def create_guard_files
11
- template 'guard.rb', File.join('app', 'guards', class_path, "#{plural_file_name}_guard.rb")
11
+ template 'guard.yml', File.join('app', 'guards', class_path, "#{plural_file_name}_guard.yml")
12
12
  end
13
-
14
- def guard_class_name
15
- controller_class_name
16
- end
17
-
18
- def aliases
19
- { :create=>:new, :update=>:edit }
20
- end
21
-
13
+
14
+ #TODO should be coming from the actual generator
22
15
  def actions
23
- ['index', 'show', 'new', 'edit', 'destroy']
16
+ ['index', 'show', 'new', 'create', 'edit', 'update', 'destroy']
24
17
  end
25
18
  end
26
19
  end
@@ -0,0 +1,12 @@
1
+ <%= plural_file_name %>:
2
+ defaults: []
3
+ <% case actions
4
+ when Array
5
+ for action in actions -%>
6
+ # <%= action %>: []
7
+ <% end
8
+ when Hash
9
+ actions.each do |action, groups| -%>
10
+ <%= action %>: <%= groups.inspect %>
11
+ <% end
12
+ end -%>
data/lib/ixtlan-guard.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'ixtlan/guard'
2
2
  if defined?(Rails)
3
3
  require 'ixtlan/guard/railtie'
4
- require 'ixtlan/guard/rails_integration'
5
4
  end
data/lib/ixtlan/guard.rb CHANGED
@@ -1 +1 @@
1
- require 'ixtlan/guard/guard'
1
+ require 'ixtlan/guard/guard_ng'
@@ -0,0 +1,55 @@
1
+ require 'yaml'
2
+ module Ixtlan
3
+ module Guard
4
+ class Config
5
+
6
+ def initialize(options = {})
7
+ @guards_dir = options[:guards_dir]
8
+ @load_method = options[:cache] ? :cached_load_from_yaml_file : :load_from_yaml_file
9
+ raise GuardException.new("guards directory does not exists: #{@guards_dir}") unless File.directory?(@guards_dir)
10
+ end
11
+
12
+ def allowed_groups(resource, action)
13
+ if resource && action
14
+ resource = resource.to_s
15
+ groups = send(@load_method, resource)
16
+ groups[action.to_s] || groups["defaults"] || []
17
+ else
18
+ []
19
+ end
20
+ end
21
+
22
+ def has_guard?(resource)
23
+ File.exists? yaml_file(resource)
24
+ end
25
+
26
+ def map_of_all
27
+ result = {}
28
+ Dir[File.join(@guards_dir, "*_guard.yml")].each do |file|
29
+ result.merge!(YAML.load_file(file))
30
+ end
31
+ result
32
+ end
33
+
34
+ private
35
+
36
+ def cached_load_from_yaml_file(resource)
37
+ @cache ||= {}
38
+ @cache[resource] ||= load_from_yaml_file(resource)
39
+ end
40
+
41
+ def yaml_file(resource)
42
+ File.join(@guards_dir, "#{resource}_guard.yml")
43
+ end
44
+
45
+ def load_from_yaml_file(resource)
46
+ file = yaml_file(resource)
47
+ if File.exists? file
48
+ YAML.load_file(file)[resource] || {}
49
+ else
50
+ {}
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,157 @@
1
+ require 'ixtlan/guard/guard_config'
2
+
3
+ module Ixtlan
4
+ module Guard
5
+ class GuardNG
6
+
7
+ def initialize(options = {})
8
+ options[:guards_dir] ||= File.expand_path(".")
9
+ @superuser = [(options[:superuser] || "root").to_s]
10
+ @config = Config.new(options)
11
+ @logger = options[:logger]
12
+ end
13
+
14
+ def block_groups(groups)
15
+ @blocked_groups = (groups || []).collect { |g| g.to_s}
16
+ @blocked_groups.delete(@superuser)
17
+ @blocked_groups
18
+ end
19
+
20
+ def blocked_groups
21
+ @blocked_groups ||= []
22
+ end
23
+
24
+ def logger
25
+ @logger ||=
26
+ if defined?(Slf4r::LoggerFactory)
27
+ Slf4r::LoggerFactory.new(Ixtlan::Guard)
28
+ else
29
+ require 'logger'
30
+ Logger.new(STDOUT)
31
+ end
32
+ end
33
+
34
+ def allowed_groups(resource, action, current_groups)
35
+ allowed = @config.allowed_groups(resource, action) - blocked_groups + @superuser
36
+ if allowed.member?('*')
37
+ current_groups
38
+ else
39
+ intersect(allowed, current_groups)
40
+ end
41
+ end
42
+
43
+ def allowed?(resource, action, current_groups, flavor = nil, &block)
44
+ current_groups = current_groups.collect { |g| g.to_s }
45
+ allowed_groups = self.allowed_groups(resource, action, current_groups)
46
+ logger.debug { "guard #{resource}##{action}: #{allowed_groups.size > 0}" }
47
+ if allowed_groups.size > 0
48
+ if block
49
+ g = allowed_groups.detect do |group|
50
+ block.call(group).member?(flavor)
51
+ end
52
+ logger.debug do
53
+ if g
54
+ "found group #{g} for #{flavor}"
55
+ else
56
+ "no group found for #{flavor}"
57
+ end
58
+ end
59
+ g != nil
60
+ else
61
+ true
62
+ end
63
+ else
64
+ unless @config.has_guard?(resource)
65
+ raise ::Ixtlan::Guard::GuardException.new("no guard config for '#{resource}'")
66
+ else
67
+ false
68
+ end
69
+ end
70
+ end
71
+
72
+ def permissions(current_groups, flavors = {})
73
+ perms = []
74
+ m = @config.map_of_all
75
+ m.each do |resource, actions|
76
+ nodes = []
77
+ perm = Node.new(:permission)
78
+ perm[:resource] = resource
79
+ perm[:actions] = nodes
80
+ defaults = intersect(current_groups, (actions.delete('defaults') || []) + @superuser)
81
+ deny = perm[:deny] = defaults.size != 0
82
+ actions.each do |action, groups|
83
+ node = Node.new(:action)
84
+ allowed_groups =
85
+ if groups && groups.member?('*')
86
+ current_groups
87
+ else
88
+ intersect(current_groups, (groups || []) + @superuser)
89
+ end
90
+ if (deny && allowed_groups.size == 0) || (!deny && allowed_groups.size > 0)
91
+ node[:name] = action
92
+ # f = {}
93
+ # flavors.each do |fl, block|
94
+ # f[fl] = block.call(allowed_groups)
95
+ # end
96
+ # node[:flavors] = f if f.size > 0
97
+ nodes << node
98
+ end
99
+ end
100
+ perms << perm
101
+ end
102
+ perms
103
+ end
104
+
105
+ def permission_map(current_groups, flavors = {})
106
+ # TODO fix it - think first !!
107
+ perms = {}
108
+ m = @config.map_of_all
109
+ m.each do |resource, actions|
110
+ nodes = {}
111
+ actions.each do |action, groups|
112
+ if action == 'defaults'
113
+ nodes[action] = {}
114
+ else
115
+ allowed_groups = intersect(current_groups, (groups || []) + @superuser)
116
+ if allowed_groups.size > 0
117
+ f = {}
118
+ flavors.each do |fl, block|
119
+ flav = block.call(allowed_groups)
120
+ f[fl] = flav if flav.size > 0
121
+ end
122
+ nodes[action] = f
123
+ else
124
+ nodes[action] = nil # indicates not default action
125
+ end
126
+ end
127
+ end
128
+ perms[resource] = nodes if nodes.size > 0
129
+ end
130
+ perms
131
+ end
132
+
133
+ private
134
+
135
+ def intersect(set1, set2)
136
+ set1 - (set1 - set2)
137
+ end
138
+ end
139
+ class Node < Hash
140
+
141
+ def initialize(name)
142
+ map = super
143
+ @content = {}
144
+ merge!({ name => @content })
145
+ end
146
+
147
+ def []=(k,v)
148
+ @content[k] = v
149
+ end
150
+ def [](k)
151
+ @content[k]
152
+ end
153
+ end
154
+ class GuardException < Exception; end
155
+ class PermissionDenied < GuardException; end
156
+ end
157
+ end
@@ -0,0 +1,76 @@
1
+ module Ixtlan
2
+ module ActionController #:nodoc:
3
+ module Guard #:nodoc:
4
+ def self.included(base)
5
+ base.send(:include, InstanceMethods)
6
+ unless base.respond_to?(:groups_for_current_user)
7
+ base.send(:include, GroupsMethod)
8
+ end
9
+ end
10
+
11
+ module GroupsMethod
12
+
13
+ protected
14
+
15
+ def groups_for_current_user
16
+ if respond_to?(:current_user) && current_user
17
+ current_user.groups.collect do |group|
18
+ group.name
19
+ end
20
+ else
21
+ []
22
+ end
23
+ end
24
+ end
25
+
26
+ module RootGroup
27
+ protected
28
+
29
+ def groups_for_current_user
30
+ ['root']
31
+ end
32
+ end
33
+
34
+ module InstanceMethods #:nodoc:
35
+
36
+ protected
37
+
38
+ def guard
39
+ Rails.application.config.guard
40
+ end
41
+
42
+ def check(flavor = nil, &block)
43
+ unless guard.allowed?(params[:controller],
44
+ params[:action],
45
+ groups_for_current_user,
46
+ flavor,
47
+ &block)
48
+ if flavor
49
+ raise ::Ixtlan::Guard::PermissionDenied.new("permission denied for '#{params[:controller]}##{params[:action]}##{flavor}'")
50
+ else
51
+ raise ::Ixtlan::Guard::PermissionDenied.new("permission denied for '#{params[:controller]}##{params[:action]}'")
52
+ end
53
+ end
54
+ true
55
+ end
56
+
57
+ def authorization
58
+ check
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ module Allowed #:nodoc:
65
+ # Inclusion hook to make #allowed available as method
66
+ def self.included(base)
67
+ base.send(:include, InstanceMethods)
68
+ end
69
+
70
+ module InstanceMethods #:nodoc:
71
+ def allowed?(resource, action)
72
+ controller.send(:guard).allowed?(resource, action, controller.send(:groups_for_current_user))
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,24 +1,30 @@
1
1
  require 'rails'
2
- require 'ixtlan/guard'
2
+ require 'ixtlan/guard/guard_ng'
3
+ require 'ixtlan/guard/guard_rails'
3
4
  require 'logger'
5
+ require 'fileutils'
4
6
 
5
7
  module Ixtlan
6
8
  module Guard
7
9
  class Railtie < Rails::Railtie
8
10
 
9
11
  config.before_configuration do |app|
10
- app.config.guard =
11
- Ixtlan::Guard::Guard.new(:guard_dir => File.join(Rails.root, "app", "guards"))
12
+ app.config.guards_dir = File.join(Rails.root, "app", "guards")
12
13
  end
13
14
 
14
15
  config.after_initialize do |app|
15
16
  logger = app.config.logger || Rails.logger || Logger.new(STDERR)
16
- app.config.guard.logger = logger unless defined?(Slf4r)
17
- begin
18
- app.config.guard.setup
19
- rescue Ixtlan::Guard::GuardException => e
20
- logger.warn e.message
21
- end
17
+ options = {
18
+ :guards_dir => app.config.guards_dir,
19
+ :cache => app.config.cache_classes
20
+ }
21
+ options[:logger] = logger unless defined?(Slf4r)
22
+ FileUtils.mkdir_p(app.config.guards_dir)
23
+ app.config.guard = Ixtlan::Guard::GuardNG.new(options)
24
+
25
+ ::ActionController::Base.send(:include, Ixtlan::ActionController::Guard)
26
+ ::ActionController::Base.send(:before_filter, :authorization)
27
+ ::ActionView::Base.send(:include, Ixtlan::Allowed)
22
28
  end
23
29
 
24
30
  config.generators do
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'ixtlan/guard/guard_ng'
3
+ require 'logger'
4
+ require 'fileutils'
5
+
6
+ $target = File.join("target", "guards", "users_guard.yml")
7
+ FileUtils.mkdir_p(File.dirname($target))
8
+ $source1 = File.join(File.dirname(__FILE__), "guards", "users1_guard.yml")
9
+ $source2 = File.join(File.dirname(__FILE__), "guards", "users2_guard.yml")
10
+ $logger = Logger.new(STDOUT)
11
+ def $logger.debug(&block)
12
+ info("\n\t[debug] " + block.call)
13
+ end
14
+
15
+ describe Ixtlan::Guard::GuardNG do
16
+
17
+ context "without caching" do
18
+ def not_cached
19
+ $not_cached ||= Ixtlan::Guard::GuardNG.new(:guards_dir => File.dirname($target),
20
+ :logger => $logger )
21
+ end
22
+
23
+ subject { not_cached }
24
+
25
+ it 'should pass' do
26
+ FileUtils.cp($source1, $target)
27
+ subject.allowed?(:users, :index, [:users]).should be_true
28
+ subject.allowed?(:users, :index, [:admin]).should be_false
29
+ end
30
+
31
+ it 'should not pass' do
32
+ FileUtils.cp($source2, $target)
33
+ subject.allowed?(:users, :index, [:users]).should be_false
34
+ subject.allowed?(:users, :index, [:admin]).should be_true
35
+ end
36
+ end
37
+
38
+ context "with caching" do
39
+ def cached
40
+ $cached ||= Ixtlan::Guard::GuardNG.new(:guards_dir => File.dirname($target),
41
+ :logger => $logger,
42
+ :cache => true)
43
+ end
44
+ subject { cached }
45
+
46
+ it 'should pass' do
47
+ FileUtils.cp($source1, $target)
48
+ subject.allowed?(:users, :index, [:users]).should be_true
49
+ subject.allowed?(:users, :index, [:admin]).should be_false
50
+ end
51
+
52
+ it 'should not pass' do
53
+ FileUtils.cp($source2, $target)
54
+ subject.allowed?(:users, :index, [:users]).should be_true
55
+ subject.allowed?(:users, :index, [:admin]).should be_false
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+ require 'ixtlan/guard/guard_ng'
3
+ require 'logger'
4
+
5
+ describe Ixtlan::Guard::GuardNG do
6
+
7
+ subject do
8
+ logger = Logger.new(STDOUT)
9
+ def logger.debug(&block)
10
+ info("\n\t[debug] " + block.call)
11
+ end
12
+ Ixtlan::Guard::GuardNG.new(:guards_dir => File.join(File.dirname(__FILE__), "guards"), :logger => logger )
13
+ end
14
+
15
+ context '#permissions' do
16
+
17
+ it 'should deny all without defaults but wildcard "*" actions' do
18
+ subject.permissions(['unknown_group']).should == [
19
+ #allow nothing
20
+ {:permission=>{:resource=>"users", :actions=>[], :deny=>false}},
21
+ {:permission=>
22
+ {
23
+ :resource=>"no_defaults",
24
+ :actions=>[{:action=>{:name=>"index"}}],
25
+ :deny=>false #allow
26
+ }
27
+ },
28
+ {
29
+ :permission=>
30
+ {
31
+ :resource=>"defaults",
32
+ :actions=>[{:action=>{:name=>"index"}}],
33
+ :deny=>false #allow
34
+ }
35
+ },
36
+ #allow nothing
37
+ {:permission=>{:resource=>"person", :actions=>[], :deny=>false}},
38
+ #allow nothing
39
+ {:permission=>{:resource=>"accounts", :actions=>[], :deny=>false}}]
40
+ end
41
+ it 'should deny some without defaults but wildcard "*" actions' do
42
+ subject.permissions(['no_admin']).should == [
43
+ #allow nothing
44
+ {:permission=>{:resource=>"users", :actions=>[], :deny=>false}},
45
+ {:permission=>
46
+ {
47
+ :resource=>"no_defaults",
48
+ :actions=>
49
+ [{:action=>{:name=>"edit"}},
50
+ {:action=>{:name=>"index"}},
51
+ {:action=>{:name=>"show"}}],
52
+ :deny=>false #allow
53
+ }
54
+ },
55
+ {
56
+ :permission=>
57
+ {
58
+ :resource=>"defaults",
59
+ :actions=>[{:action=>{:name=>"index"}}],
60
+ :deny=>false #allow
61
+ }
62
+ },
63
+ #allow nothing
64
+ {:permission=>{:resource=>"person", :actions=>[], :deny=>false}},
65
+ #allow nothing
66
+ {:permission=>{:resource=>"accounts", :actions=>[], :deny=>false}}]
67
+ end
68
+ it 'should allow "root"' do
69
+ subject.permissions(['root']).should == [
70
+ {:permission=>{:resource=>"users", :actions=>[], :deny=>true}},
71
+ {:permission=>{:resource=>"no_defaults", :actions=>[], :deny=>true}},
72
+ {:permission=>{:resource=>"defaults", :actions=>[], :deny=>true}},
73
+ {:permission=>{:resource=>"person", :actions=>[], :deny=>true}},
74
+ {:permission=>{:resource=>"accounts", :actions=>[], :deny=>true}}]
75
+ end
76
+ it 'should allow with default group' do
77
+ subject.permissions(['_master']).should == [
78
+ #allow nothing
79
+ {:permission=>{:resource=>"users", :actions=>[], :deny=>false}},
80
+ {:permission=>
81
+ {
82
+ :resource=>"no_defaults",
83
+ :actions=>[{:action=>{:name=>"index"}}],
84
+ :deny=>false #allow
85
+ }
86
+ },
87
+ {
88
+ :permission=>
89
+ {
90
+ :resource=>"defaults",
91
+ :actions=>[{:action=>{:name=>"show"}},
92
+ {:action=>{:name=>"destroy"}}],
93
+ :deny=>true
94
+ }
95
+ },
96
+ #allow nothing
97
+ {:permission=>{:resource=>"person", :actions=>[], :deny=>false}},
98
+ #allow nothing
99
+ {:permission=>{:resource=>"accounts", :actions=>[], :deny=>false}}]
100
+ end
101
+ it 'should allow with non-default group' do
102
+ subject.permissions(['_admin']).should == [
103
+ #allow nothing
104
+ {:permission=>{:resource=>"users", :actions=>[], :deny=>false}},
105
+ {:permission=>
106
+ {
107
+ :resource=>"no_defaults",
108
+ :actions=>[{:action=>{:name=>"index"}}],
109
+ :deny=>false #allow
110
+ }
111
+ },
112
+ {
113
+ :permission=>
114
+ {
115
+ :resource=>"defaults",
116
+ :actions=>[{:action=>{:name=>"edit"}},
117
+ {:action=>{:name=>"index"}},
118
+ {:action=>{:name=>"show"}}],
119
+ :deny=>false # allow
120
+ }
121
+ },
122
+ #allow nothing
123
+ {:permission=>{:resource=>"person", :actions=>[], :deny=>false}},
124
+ #allow nothing
125
+ {:permission=>{:resource=>"accounts", :actions=>[], :deny=>false}}]
126
+ end
127
+ end
128
+
129
+ context '#permission_map' do
130
+ it 'should export' do
131
+ pending "check expectations before implementing specs"
132
+ subject.permission_map(['admin']).should == {"users"=>{"defaults"=>nil}, "person"=>{"defaults"=>nil, "destroy"=>{}, "index"=>{}}, "accounts"=>{"defaults"=>nil, "destroy"=>{}, "show"=>nil}}
133
+
134
+ subject.permission_map(['manager']).should == {"users"=>{"defaults"=>nil}, "person"=>{"defaults"=>nil, "destroy"=>nil, "index"=>{}}, "accounts"=>{"defaults"=>nil, "destroy"=>nil, "show"=>{}}}
135
+
136
+ subject.permission_map(['manager', 'admin']).should == {"users"=>{"defaults"=>nil}, "person"=>{"defaults"=>nil, "destroy"=>{}, "index"=>{}}, "accounts"=>{"defaults"=>nil, "destroy"=>{}, "show"=>{}}}
137
+
138
+ subject.permission_map(['users']).should == {"users"=>{"defaults"=>{}}, "person"=>{"defaults"=>nil, "destroy"=>nil, "index"=>nil}, "accounts"=>{"defaults"=>nil, "destroy"=>nil, "show"=>nil}}
139
+ end
140
+
141
+ it 'should export with flavor' do
142
+ pending "check expectations before implementing specs"
143
+
144
+ flavors = { 'admin' => ['example', 'dummy'], 'manager' => ['example', 'master'] }
145
+
146
+ domains = Proc.new do |groups|
147
+ groups.collect do |g|
148
+ flavors[g] || []
149
+ end.flatten.uniq
150
+ end
151
+
152
+ subject.permission_map(['admin'], 'domains' => domains).should == {"users"=>{"defaults"=>nil}, "person"=>{"defaults"=>nil, "destroy"=>{'domains'=>["example", "dummy"]}, "index"=>{'domains'=>["example", "dummy"]}}, "accounts"=>{"defaults"=>nil, "destroy"=>{'domains'=>["example", "dummy"]}, "show"=>nil}}
153
+
154
+ subject.permission_map(['manager'], 'domains' => domains).should == {"users"=>{"defaults"=>nil}, "person"=>{"defaults"=>nil, "destroy"=>nil, "index"=>{"domains"=>["example", "master"]}}, "accounts"=>{"defaults"=>nil, "destroy"=>nil, "show"=>{"domains"=>["example", "master"]}}}
155
+
156
+ subject.permission_map(['manager', 'admin'], 'domains' => domains).should == {"users"=>{"defaults"=>nil}, "person"=>{"defaults"=>nil, "destroy"=>{"domains"=>["example", "dummy"]}, "index"=>{"domains"=>["example", "master", "dummy"]}}, "accounts"=>{"defaults"=>nil, "destroy"=>{"domains"=>["example", "dummy"]}, "show"=>{"domains"=>["example", "master"]}}}
157
+
158
+ subject.permission_map(['users'], 'domains' => domains).should == {"users"=>{"defaults"=>{}}, "person"=>{"defaults"=>nil, "destroy"=>nil, "index"=>nil}, "accounts"=>{"defaults"=>nil, "destroy"=>nil, "show"=>nil}}
159
+ end
160
+ end
161
+ end
data/spec/guard_spec.rb CHANGED
@@ -1,130 +1,89 @@
1
1
  require 'spec_helper'
2
- require 'ixtlan/guard'
3
-
4
- describe Ixtlan::Guard do
5
-
6
- before :all do
7
- @guard = Ixtlan::Guard::Guard.new(:guard_dir => File.join(File.dirname(__FILE__), "guards") )
8
-
9
- @guard.setup
10
- @current_user = Object.new
11
- def @current_user.groups(g = nil)
12
- if g
13
- @groups = g.collect do |gg|
14
- group = Object.new
15
- def group.name(name =nil)
16
- @name = name if name
17
- @name
18
- end
19
- group.name(gg)
20
- group
21
- end
22
- end
23
- @groups || []
24
- end
2
+ require 'ixtlan/guard/guard_ng'
3
+ require 'logger'
4
+
5
+ describe Ixtlan::Guard::GuardNG do
25
6
 
26
- @controller = Object.new
27
- def @controller.current_user(u = nil)
28
- @u = u if u
29
- @u
7
+ subject do
8
+ logger = Logger.new(STDOUT)
9
+ def logger.debug(&block)
10
+ info("\n\t[debug] " + block.call)
30
11
  end
31
- @controller.current_user( @current_user )
12
+ Ixtlan::Guard::GuardNG.new(:guards_dir => File.join(File.dirname(__FILE__), "guards"), :logger => logger )
32
13
  end
33
14
 
34
15
  it 'should fail with missing guard dir' do
35
- lambda {Ixtlan::Guard::Guard.new(:guard_dir => "does_not_exists").setup }.should raise_error(Ixtlan::Guard::GuardException)
16
+ lambda {Ixtlan::Guard::GuardNG.new(:guards_dir => "does_not_exists") }.should raise_error(Ixtlan::Guard::GuardException)
36
17
  end
37
18
 
38
19
  it 'should initialize' do
39
- @guard.should_not be_nil
40
- end
41
-
42
- it 'should fail check without current user' do
43
- controller = Object.new
44
- def controller.current_user
45
- end
46
- @guard.check(controller, :none, :something).should be_false
20
+ subject.should_not be_nil
47
21
  end
48
22
 
49
- it 'should pass check with user being root' do
50
- @current_user.groups([:root])
51
- @guard.check(@controller, :users, :show).should be_true
23
+ it 'should fail without groups' do
24
+ subject.allowed?(:users, :something, []).should be_false
52
25
  end
53
26
 
54
- it 'should not pass check with user - no groups' do
55
- @current_user.groups([])
56
- @guard.check(@controller, :users, :show).should be_false
27
+ it 'should pass with user being root' do
28
+ subject.allowed?(:users, :show, [:root]).should be_true
57
29
  end
58
30
 
59
- it 'should pass unguarded check with user - no groups' do
60
- @current_user.groups([])
61
- @guard.check(@controller, :users, :index).should be_true
31
+ it 'should pass "allow all groups" with user with any groups' do
32
+ subject.allowed?(:users, :index, [:any]).should be_true
62
33
  end
63
34
 
64
- it 'should pass check with user on aliased action' do
65
- @current_user.groups([:users])
66
- @guard.check(@controller, :users, :edit).should be_true
35
+ it 'should pass' do
36
+ subject.allowed?(:users, :update, [:users]).should be_true
67
37
  end
68
38
 
69
- it 'should pass check with user' do
70
- @current_user.groups([:users])
71
- @guard.check(@controller, :users, :update).should be_true
72
- end
73
-
74
- it 'should not pass check with user when in blocked group' do
75
- @current_user.groups([:users])
76
- @guard.block_groups([:users])
39
+ it 'should not pass with user when in blocked group' do
40
+ subject.block_groups([:users])
77
41
  begin
78
- @guard.check(@controller, :users, :update).should be_false
42
+ subject.allowed?(:users, :update, [:users]).should be_false
79
43
  ensure
80
- @guard.block_groups([])
44
+ subject.block_groups([])
81
45
  end
82
46
  end
83
47
 
84
- it 'should pass check with user when not in blocked group' do
85
- @current_user.groups([:users])
86
- @guard.block_groups([:accounts])
48
+ it 'should pass with user when not in blocked group' do
49
+ subject.block_groups([:accounts])
87
50
  begin
88
- @guard.check(@controller, :users, :update).should be_true
51
+ subject.allowed?(:users, :update, [:users]).should be_true
89
52
  ensure
90
- @guard.block_groups([])
53
+ subject.block_groups([])
91
54
  end
92
55
  end
93
56
 
94
- it 'should pass check with root-user when not in blocked group' do
95
- @current_user.groups([:root])
96
- @guard.block_groups([:root])
57
+ it 'should not block root group' do
58
+ subject.block_groups([:root])
97
59
  begin
98
- @guard.check(@controller, :users, :update).should be_true
60
+ subject.allowed?(:users, :update, [:root]).should be_true
99
61
  ensure
100
- @guard.block_groups([])
62
+ subject.block_groups([])
101
63
  end
102
64
  end
103
65
 
104
- it 'should not pass check with user' do
105
- @current_user.groups([:accounts])
106
- @guard.check(@controller, :users, :update).should be_false
66
+ it 'should not pass' do
67
+ subject.allowed?(:users, :update, [:accounts]).should be_false
68
+ end
69
+
70
+ it 'should should use defaults on unknown action' do
71
+ subject.allowed?(:users, :unknow, [:users]).should be_true
107
72
  end
108
73
 
109
- it 'should pass check with user with passing extra check' do
110
- @current_user.groups([:users])
111
- @guard.check(@controller, :users, :update) do |g|
112
- true
113
- end.should be_true
74
+ it 'should pass with right group and allowed flavor' do
75
+ subject.allowed?(:users, :update, [:users], :example){ |g| [:example]}.should be_true
114
76
  end
115
77
 
116
- it 'should not pass check with user with failing extra check' do
117
- @current_user.groups([:users])
118
- @guard.check(@controller, :users, :update) do |g|
119
- false
120
- end.should be_false
78
+ it 'should not pass with wrong group but allowed flavor' do
79
+ subject.allowed?(:users, :update, [:accounts], :example){ |g| [:example]}.should be_false
121
80
  end
122
81
 
123
- it 'should raise exception on unknown action' do
124
- lambda {@guard.check(@controller, :users, :unknown_action) }.should raise_error(Ixtlan::Guard::GuardException)
82
+ it 'should not pass with wrong group but disallowed flavor' do
83
+ subject.allowed?(:users, :update, [:accounts], :example){ |g| []}.should be_false
125
84
  end
126
85
 
127
- it 'should raise exception on unknown resource' do
128
- lambda {@guard.check(@controller, :unknown_resource, :update) }.should raise_error(Ixtlan::Guard::GuardException)
86
+ it 'should not pass with right group and disallowed flavor' do
87
+ subject.allowed?(:users, :update, [:users], :example){ |g| []}.should be_false
129
88
  end
130
89
  end
@@ -0,0 +1,6 @@
1
+ accounts:
2
+ defaults:
3
+ - group
4
+ destroy:
5
+ - admin
6
+ show: [manager, guest]
@@ -0,0 +1,6 @@
1
+ defaults:
2
+ defaults: [_master]
3
+ edit: [_admin, _master]
4
+ index: [*]
5
+ show: [_admin]
6
+ destroy:
@@ -0,0 +1,5 @@
1
+ no_defaults:
2
+ edit: [no_admin, no_master]
3
+ index: [*]
4
+ show: [no_admin]
5
+ destroy:
@@ -0,0 +1,7 @@
1
+ person:
2
+ defaults:
3
+ - group1
4
+ - group2
5
+ destroy:
6
+ - admin
7
+ index: [admin, manager, guest]
@@ -0,0 +1,2 @@
1
+ users:
2
+ defaults: [users]
@@ -0,0 +1,3 @@
1
+ users:
2
+ defaults: [users]
3
+ index: [admin]
@@ -0,0 +1,3 @@
1
+ users:
2
+ defaults: [users]
3
+ index: [*]
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ixtlan-guard
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.5.0
5
+ version: 0.6.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - mkristian
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-08-06 00:00:00 +05:30
13
+ date: 2011-09-05 00:00:00 +05:30
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -19,9 +19,12 @@ dependencies:
19
19
  requirement: &id001 !ruby/object:Gem::Requirement
20
20
  none: false
21
21
  requirements:
22
- - - "="
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.6.0
25
+ - - <
23
26
  - !ruby/object:Gem::Version
24
- version: 0.5.0
27
+ version: 0.6.99999
25
28
  type: :runtime
26
29
  version_requirements: *id001
27
30
  - !ruby/object:Gem::Dependency
@@ -96,8 +99,7 @@ files:
96
99
  - lib/generators/guard/controller/controller_generator.rb
97
100
  - lib/generators/guard/scaffold/USAGE
98
101
  - lib/generators/guard/scaffold/scaffold_generator.rb
99
- - lib/generators/guard/scaffold/templates/guard.rb
100
- - lib/generators/guard/templates/guard.rb
102
+ - lib/generators/guard/templates/guard.yml
101
103
  - lib/generators/ixtlan/user_management_scaffold/user_management_scaffold_generator.rb
102
104
  - lib/generators/ixtlan/user_management_controller/USAGE
103
105
  - lib/generators/ixtlan/user_management_controller/user_management_controller_generator.rb
@@ -114,26 +116,34 @@ files:
114
116
  - lib/generators/active_record/templates/group_user_migration.rb
115
117
  - lib/generators/active_record/templates/flavor_model.rb
116
118
  - lib/ixtlan/guard.rb
117
- - lib/ixtlan/guard/rails_integration.rb
118
- - lib/ixtlan/guard/guard.rb
119
+ - lib/ixtlan/guard/guard_ng.rb
120
+ - lib/ixtlan/guard/guard_config.rb
121
+ - lib/ixtlan/guard/guard_rails.rb
119
122
  - lib/ixtlan/guard/railtie.rb
120
123
  - lib/ixtlan/guard/controllers/maintenance_controller.rb
121
124
  - lib/ixtlan/guard/controllers/permissions_controller.rb
122
125
  - lib/ixtlan/guard/spec/user_management_models_spec.rb
123
126
  - lib/ixtlan/guard/models/maintenance.rb
124
127
  - lib/ixtlan/guard/models/user_update_manager.rb
128
+ - spec/guard_export_spec.rb
125
129
  - spec/spec_helper.rb
130
+ - spec/guard_cache_spec.rb
126
131
  - spec/guard_spec.rb
127
132
  - spec/railtie_spec.rb
128
- - spec/guards/users_guard.rb
133
+ - spec/guards/users_guard.yml
134
+ - spec/guards/users2_guard.yml
135
+ - spec/guards/no_defaults_guard.yml
136
+ - spec/guards/defaults_guard.yml
137
+ - spec/guards/users1_guard.yml
138
+ - spec/guards/person_guard.yml
139
+ - spec/guards/accounts_guard.yml
129
140
  has_rdoc: true
130
141
  homepage: http://github.com/mkristian/ixtlan-guard
131
142
  licenses:
132
143
  - MIT-LICENSE
133
144
  post_install_message:
134
- rdoc_options:
135
- - --main
136
- - README.textile
145
+ rdoc_options: []
146
+
137
147
  require_paths:
138
148
  - lib
139
149
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -156,5 +166,7 @@ signing_key:
156
166
  specification_version: 3
157
167
  summary: guard your controller actions
158
168
  test_files:
169
+ - spec/guard_export_spec.rb
170
+ - spec/guard_cache_spec.rb
159
171
  - spec/guard_spec.rb
160
172
  - spec/railtie_spec.rb
@@ -1,20 +0,0 @@
1
- class <%= guard_class_name %>Guard
2
- def initialize(guard)
3
- #guard.name = "<%= plural_file_name %>"
4
- <% if aliases -%>
5
- guard.aliases = <%= aliases.inspect %>
6
- <% end -%>
7
- guard.action_map= {
8
- <% case actions
9
- when Array
10
- for action in actions -%>
11
- :<%= action %> => [],
12
- <% end
13
- when Hash
14
- actions.each do |action, groups| -%>
15
- :<%= action %> => <%= groups.inspect %>,
16
- <% end
17
- end -%>
18
- }
19
- end
20
- end
@@ -1,20 +0,0 @@
1
- class <%= guard_class_name %>Guard
2
- def initialize(guard)
3
- #guard.name = "<%= plural_file_name %>"
4
- <% if aliases -%>
5
- guard.aliases = <%= aliases.inspect %>
6
- <% end -%>
7
- guard.action_map= {
8
- <% case actions
9
- when Array
10
- for action in actions -%>
11
- :<%= action %> => [],
12
- <% end
13
- when Hash
14
- actions.each do |action, groups| -%>
15
- :<%= action %> => <%= groups.inspect %>,
16
- <% end
17
- end -%>
18
- }
19
- end
20
- end
@@ -1,247 +0,0 @@
1
- module Ixtlan
2
- module Guard
3
- class ControllerGuard
4
-
5
- attr_accessor :name, :action_map, :aliases, :flavor
6
-
7
- def initialize(name)
8
- @name = name.sub(/_guard$/, '').to_sym
9
- class_name = name.split(/\//).collect { |part| part.split("_").each { |pp| pp.capitalize! }.join }.join("::")
10
- Object.const_get(class_name).new(self)
11
- end
12
-
13
- def flavor=(flavor)
14
- @flavor = flavor.to_sym
15
- end
16
-
17
- def name=(name)
18
- @name = name.to_sym
19
- end
20
-
21
- def aliases=(map)
22
- @aliases = symbolize(map)
23
- end
24
-
25
- def action_map=(map)
26
- @action_map = symbolize(map)
27
- end
28
-
29
- private
30
-
31
- def symbolize(h)
32
- result = {}
33
-
34
- h.each do |k, v|
35
- if v.is_a?(Hash)
36
- result[k.to_sym] = symbolize_keys(v) unless v.size == 0
37
- elsif v.is_a?(Array)
38
- val = []
39
- v.each {|vv| val << vv.to_sym }
40
- result[k.to_sym] = val
41
- else
42
- result[k.to_sym] = v.to_sym
43
- end
44
- end
45
-
46
- result
47
- end
48
-
49
- end
50
-
51
- class Guard
52
-
53
- attr_accessor :logger, :guard_dir, :superuser, :groups_of_current_user
54
-
55
- def initialize(options, &block)
56
- @superuser = (options[:superuser] || :root).to_sym
57
- @guard_dir = options[:guard_dir] || File.join("app", "guards")
58
- @user_groups = (options[:user_groups] || :groups).to_sym
59
- @user_groups_name = (options[:user_groups_name] || :name).to_sym
60
-
61
- @map = {}
62
- @aliases = {}
63
- @flavor_map = {}
64
-
65
- @groups_of_current_user =
66
- if block
67
- block
68
- else
69
- Proc.new do |controller|
70
- # get the groups of the current_user
71
- user = controller.send(:current_user) if controller.respond_to?(:current_user)
72
- if user
73
- (user.send(@user_groups) || []).collect do |group|
74
- name = group.send(@user_groups_name)
75
- name.to_sym if name
76
- end
77
- end
78
- end
79
- end
80
- end
81
-
82
- def logger
83
- @logger ||= if defined?(Slf4r::LoggerFactory)
84
- Slf4r::LoggerFactory.new(Ixtlan::Guard)
85
- else
86
- require 'logger'
87
- Logger.new(STDOUT)
88
- end
89
- end
90
-
91
- def setup
92
- if File.exists?(@guard_dir)
93
- Dir.new(guard_dir).to_a.each do |f|
94
- if f.match(".rb$")
95
- require(File.join(guard_dir, f))
96
- controller_guard = ControllerGuard.new(f.sub(/.rb$/, ''))
97
- register(controller_guard)
98
- end
99
- end
100
- logger.debug("initialized guard . . .")
101
- else
102
- raise GuardException.new("guard directory #{guard_dir} not found, skip loading")
103
- end
104
- end
105
-
106
- private
107
-
108
- def register(controller_guard)
109
- msg = (controller_guard.aliases || {}).collect {|k,v| "\n\t#{k} == #{v}"} + controller_guard.action_map.collect{ |k,v| "\n\t#{k} => [#{v.join(',')}]"}
110
- logger.debug("#{controller_guard.name} guard: #{msg}")
111
- @map[controller_guard.name] = controller_guard.action_map
112
- @aliases[controller_guard.name] = controller_guard.aliases || {}
113
- @flavor_map[controller_guard.name] = controller_guard.flavor if controller_guard.flavor
114
- end
115
-
116
- public
117
-
118
- def flavor(controller)
119
- @flavor_map[controller.params[:controller].to_sym]
120
- end
121
-
122
- def block_groups(groups)
123
- @blocked_groups = (groups || []).collect { |g| g.to_sym}
124
- @blocked_groups.delete(@superuser)
125
- @blocked_groups
126
- end
127
-
128
- def blocked_groups
129
- @blocked_groups ||= []
130
- end
131
-
132
- def current_user_restricted?(controller)
133
- groups = @groups_of_current_user.call(controller)
134
- if groups
135
- # groups.select { |g| !blocked_groups.member?(g.to_sym) }.size < groups.size
136
- (groups - blocked_groups).size < groups.size
137
- else
138
- nil
139
- end
140
- end
141
-
142
- def permissions(controller)
143
- groups = (@groups_of_current_user.call(controller) || []).collect do
144
- |g| g.to_sym
145
- end
146
- map = {}
147
- @map.each do |resource, action_map|
148
- action_map.each do |action, allowed|
149
- if allowed.member? :*
150
- allowed = groups.dup
151
- end
152
- allowed << @superuser unless allowed.member? @superuser
153
-
154
- # intersection of allowed and groups empty ?
155
- if (allowed - groups).size < allowed.size
156
- permission = (map[resource] ||= {})
157
- permission[:resource] = resource
158
- actions = (permission[:actions] ||= [])
159
- action_node = {:name => action}
160
- flavors.each do |flavor, block|
161
- flavor_list = []
162
- (allowed - (allowed - groups)).each do |group|
163
- list = block.call(controller, group)
164
- # union - no duplicates
165
- flavor_list = flavor_list - list + list
166
- end
167
- action_node[flavor.to_s.sub(/s$/, '') + "s"] = flavor_list if flavor_list.size > 0
168
- end
169
- actions << { :action => action_node }
170
- actions << @aliases[resource][action] if @aliases[resource][action]
171
- end
172
- end
173
- end
174
-
175
- result = map.values.collect do |perm|
176
- { :permission => perm }
177
- end
178
- result.class_eval "alias :to_x :to_xml" unless map.respond_to? :to_x
179
- def result.to_xml(options = {}, &block)
180
- options[:root] = :permissions unless options[:root]
181
- to_x(options, &block)
182
- end
183
-
184
- def result.to_json(options = {}, &block)
185
- {:permissions => self}.to_json(options, &block)
186
- end
187
- result
188
- end
189
-
190
- def flavors
191
- @flavors ||= {}
192
- end
193
-
194
- def register_flavor(flavor, &block)
195
- flavors[flavor.to_sym] = block
196
- end
197
-
198
- def check(controller, resource, action, flavor_selector = nil, &block)
199
- resource = resource.to_sym
200
- action = action.to_sym
201
- groups = @groups_of_current_user.call(controller)
202
- if groups.nil?
203
- logger.debug("check #{resource}##{action}: not authenticated")
204
- return false
205
- end
206
- if (@map.key? resource)
207
- action = @aliases[resource][action] || action
208
- allowed = @map[resource][action]
209
- if (allowed.nil?)
210
- logger.warn("unknown action '#{action}' for controller '#{resource}'")
211
- raise ::Ixtlan::Guard::GuardException.new("unknown action '#{action}' for controller '#{resource}'")
212
- else
213
- allowed << @superuser unless allowed.member? @superuser
214
- allow_all_groups = allowed.member?(:*)
215
- if(allow_all_groups && block.nil?)
216
- logger.debug("check #{resource}##{action}: allowed for all")
217
- return true
218
- else
219
- groups.each do |group|
220
- if (allow_all_groups || allowed.member?(group.to_sym)) && !blocked_groups.member?(group.to_sym)
221
- flavor_for_resource = flavors[@flavor_map[resource]]
222
- if block.nil?
223
- if(flavor_for_resource && flavor_for_resource.call(controller, group).member?(flavor_selector.to_s) || flavor_for_resource.nil?)
224
- logger.debug("check #{resource}##{action}: true")
225
- return true
226
- end
227
- elsif block.call(group)
228
- logger.debug("check #{resource}##{action}: true")
229
- return true
230
- end
231
- end
232
- end
233
- end
234
- logger.debug("check #{resource}##{action}: false")
235
- return false
236
- end
237
- else
238
- logger.warn("unknown controller for '#{resource}'")
239
- raise ::Ixtlan::Guard::GuardException.new("unknown controller for '#{resource}'")
240
- end
241
- end
242
- end
243
-
244
- class GuardException < Exception; end
245
- class PermissionDenied < GuardException; end
246
- end
247
- end
@@ -1,88 +0,0 @@
1
- require 'ixtlan/guard'
2
- module Ixtlan
3
- module ActionController #:nodoc:
4
- module Guard #:nodoc:
5
- def self.included(base)
6
- base.send(:include, InstanceMethods)
7
- end
8
- module InstanceMethods #:nodoc:
9
-
10
- protected
11
-
12
- def guard
13
- Rails.application.config.guard
14
- end
15
-
16
- def authorization(flavor = nil, &block)
17
- if flavor.nil?
18
- flavor = guard.flavor(self)
19
- if flavor
20
- method = "#{flavor}_authorization".to_sym
21
- if self.respond_to?(method)
22
- return send "#{flavor}_authorization".to_sym, &block
23
- else
24
- logger.warn "flavor #{flavor} configured in guard, but there is not method '#{method}'"
25
- flavor = nil
26
- end
27
- end
28
- end
29
- resource_authorization(params[:controller], params[:action], flavor, &block)
30
- end
31
-
32
- def resource_authorization(resource, action, flavor = nil, &block)
33
- unless guard.check(self,
34
- resource,
35
- action,
36
- &flavored_block(flavor, &block))
37
- raise ::Ixtlan::Guard::PermissionDenied.new("permission denied for '#{resource}##{action}'")
38
- end
39
- true
40
- end
41
-
42
- def flavored_block(flavor = nil, &block)
43
- if block
44
- if flavor
45
- Proc.new do |group|
46
- allowed_flavors = guard.flavors[flavor.to_sym].call(self, group)
47
- block.call(allowed_flavors)
48
- end
49
- else
50
- block
51
- end
52
- end
53
- end
54
-
55
- private :flavored_block
56
-
57
- def allowed?(action, flavor = nil, &block)
58
- guard.check(self,
59
- params[:controller],
60
- action,
61
- &flavored_block(flavor, &block))
62
- end
63
- end
64
- end
65
- end
66
-
67
- module Allowed #:nodoc:
68
- # Inclusion hook to make #allowed available as method
69
- def self.included(base)
70
- base.send(:include, InstanceMethods)
71
- end
72
-
73
- module InstanceMethods #:nodoc:
74
- def allowed?(resource, action, flavor_selector = nil, &block)
75
- controller.send(:guard).check(controller, resource, action, flavor_selector, &block)
76
- end
77
- end
78
- end
79
- end
80
-
81
- ActionController::Base.send(:include, Ixtlan::ActionController::Guard)
82
- ActionController::Base.send(:before_filter, :authorization)
83
- ActionView::Base.send(:include, Ixtlan::Allowed)
84
- module Erector
85
- class Widget
86
- include Ixtlan::Allowed
87
- end
88
- end
@@ -1,13 +0,0 @@
1
- class UsersGuard
2
- def initialize(guard)
3
- guard.name = "users"
4
- guard.aliases= {:edit => :update}
5
- guard.action_map= {
6
- :index => [:*],
7
- :show => [:users],
8
- :create => [:users],
9
- :update => [:users],
10
- :destroy => [:users]
11
- }
12
- end
13
- end