ixtlan-guard 0.5.0 → 0.6.0

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.
@@ -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