ixtlan-guard 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/lib/generators/active_record/templates/flavor_migration.rb +13 -0
  3. data/lib/generators/active_record/templates/flavor_model.rb +8 -0
  4. data/lib/generators/active_record/templates/group_model.rb +43 -0
  5. data/lib/generators/active_record/templates/group_user_migration.rb +13 -0
  6. data/lib/generators/active_record/templates/user_model.rb +124 -0
  7. data/lib/generators/active_record/user_management_models_generator.rb +202 -0
  8. data/lib/generators/erb/user_management_controller_generator.rb +10 -0
  9. data/lib/generators/{ixtlan → guard}/controller/USAGE +0 -0
  10. data/lib/generators/{ixtlan → guard}/controller/controller_generator.rb +2 -2
  11. data/lib/generators/{ixtlan → guard}/scaffold/USAGE +0 -0
  12. data/lib/generators/{ixtlan → guard}/scaffold/scaffold_generator.rb +4 -6
  13. data/lib/generators/guard/scaffold/templates/guard.rb +20 -0
  14. data/lib/generators/guard/templates/guard.rb +20 -0
  15. data/lib/generators/ixtlan/maintenance_scaffold/USAGE +8 -0
  16. data/lib/generators/ixtlan/maintenance_scaffold/maintenance_scaffold_generator.rb +40 -0
  17. data/lib/generators/ixtlan/permissions_scaffold/USAGE +8 -0
  18. data/lib/generators/ixtlan/permissions_scaffold/permissions_scaffold_generator.rb +33 -0
  19. data/lib/generators/ixtlan/user_management_controller/USAGE +8 -0
  20. data/lib/generators/ixtlan/user_management_controller/user_management_controller_generator.rb +23 -0
  21. data/lib/generators/ixtlan/user_management_models/USAGE +8 -0
  22. data/lib/generators/ixtlan/user_management_models/user_management_models_generator.rb +19 -0
  23. data/lib/generators/ixtlan/user_management_scaffold/user_management_scaffold_generator.rb +13 -0
  24. data/lib/ixtlan-guard.rb +2 -2
  25. data/lib/ixtlan/guard.rb +1 -159
  26. data/lib/ixtlan/guard/controllers/maintenance_controller.rb +45 -0
  27. data/lib/ixtlan/guard/controllers/permissions_controller.rb +41 -0
  28. data/lib/ixtlan/guard/guard.rb +245 -0
  29. data/lib/ixtlan/guard/models/maintenance.rb +55 -0
  30. data/lib/ixtlan/guard/models/user_update_manager.rb +95 -0
  31. data/lib/ixtlan/guard/rails_integration.rb +88 -0
  32. data/lib/ixtlan/guard/railtie.rb +33 -0
  33. data/lib/ixtlan/guard/spec/user_management_models_spec.rb +193 -0
  34. data/spec/guard_spec.rb +48 -12
  35. data/spec/railtie_spec.rb +1 -1
  36. metadata +75 -52
  37. data/lib/generators/ixtlan/templates/edit.html.erb +0 -10
  38. data/lib/generators/ixtlan/templates/guard.rb +0 -13
  39. data/lib/generators/ixtlan/templates/index.html.erb +0 -35
  40. data/lib/generators/ixtlan/templates/new.html.erb +0 -7
  41. data/lib/generators/ixtlan/templates/show.html.erb +0 -16
  42. data/lib/generators/scaffold/scaffold/scaffold_generator.rb +0 -23
  43. data/lib/ixtlan/guard_railtie.rb +0 -43
  44. data/lib/ixtlan/rails_integration.rb +0 -55
@@ -0,0 +1,41 @@
1
+ module Ixtlan
2
+ module Guard
3
+ module Controllers
4
+ module PermissionsController
5
+
6
+ # GET /permissions
7
+ # GET /permissions.xml
8
+ # GET /permissions.json
9
+ def index
10
+ respond_to do |format|
11
+ format.html
12
+ format.xml { render :xml => guard.permissions(self).to_xml }
13
+ format.json { render :json => guard.permissions(self).to_json }
14
+ end
15
+ end
16
+
17
+ # GET /permissions/1
18
+ # GET /permissions/1.xml
19
+ # GET /permissions/1.json
20
+ def show
21
+ controller = Object.new
22
+ def controller.current_user(u = nil)
23
+ @u = u if u
24
+ @u
25
+ end
26
+ if defined? ::DataMapper
27
+ controller.current_user(current_user.class.get(params[:id]))
28
+ else
29
+ controller.current_user(current_user.class.find(params[:id]))
30
+ end
31
+
32
+ respond_to do |format|
33
+ format.html
34
+ format.xml { render :xml => guard.permissions(controller).to_xml }
35
+ format.json { render :json => guard.permissions(controller).to_json }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,245 @@
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_node
170
+ actions << @aliases[resource][action] if @aliases[resource][action]
171
+ end
172
+ end
173
+ end
174
+
175
+ result = map.values
176
+ result.class_eval "alias :to_x :to_xml" unless map.respond_to? :to_x
177
+ def result.to_xml(options = {}, &block)
178
+ options[:root] = :permissions unless options[:root]
179
+ to_x(options, &block)
180
+ end
181
+
182
+ def result.to_json(options = {}, &block)
183
+ {:permissions => self}.to_json(options, &block)
184
+ end
185
+ result
186
+ end
187
+
188
+ def flavors
189
+ @flavors ||= {}
190
+ end
191
+
192
+ def register_flavor(flavor, &block)
193
+ flavors[flavor.to_sym] = block
194
+ end
195
+
196
+ def check(controller, resource, action, flavor_selector = nil, &block)
197
+ resource = resource.to_sym
198
+ action = action.to_sym
199
+ groups = @groups_of_current_user.call(controller)
200
+ if groups.nil?
201
+ logger.debug("check #{resource}##{action}: not authenticated")
202
+ return false
203
+ end
204
+ if (@map.key? resource)
205
+ action = @aliases[resource][action] || action
206
+ allowed = @map[resource][action]
207
+ if (allowed.nil?)
208
+ logger.warn("unknown action '#{action}' for controller '#{resource}'")
209
+ raise ::Ixtlan::Guard::GuardException.new("unknown action '#{action}' for controller '#{resource}'")
210
+ else
211
+ allowed << @superuser unless allowed.member? @superuser
212
+ allow_all_groups = allowed.member?(:*)
213
+ if(allow_all_groups && block.nil?)
214
+ logger.debug("check #{resource}##{action}: allowed for all")
215
+ return true
216
+ else
217
+ groups.each do |group|
218
+ if (allow_all_groups || allowed.member?(group.to_sym)) && !blocked_groups.member?(group.to_sym)
219
+ flavor_for_resource = flavors[@flavor_map[resource]]
220
+ if block.nil?
221
+ if(flavor_for_resource && flavor_for_resource.call(controller, group).member?(flavor_selector.to_s) || flavor_for_resource.nil?)
222
+ logger.debug("check #{resource}##{action}: true")
223
+ return true
224
+ end
225
+ elsif block.call(group)
226
+ logger.debug("check #{resource}##{action}: true")
227
+ return true
228
+ end
229
+ end
230
+ end
231
+ end
232
+ logger.debug("check #{resource}##{action}: false")
233
+ return false
234
+ end
235
+ else
236
+ logger.warn("unknown controller for '#{resource}'")
237
+ raise ::Ixtlan::Guard::GuardException.new("unknown controller for '#{resource}'")
238
+ end
239
+ end
240
+ end
241
+
242
+ class GuardException < Exception; end
243
+ class PermissionDenied < GuardException; end
244
+ end
245
+ end
@@ -0,0 +1,55 @@
1
+ unless String.respond_to? "plural"
2
+ class String
3
+ def plural
4
+ self + "s"
5
+ end
6
+ end
7
+ end
8
+
9
+ module Ixtlan
10
+ module SerializableModel
11
+ def self.included(model)
12
+ model.send :include, ActiveModel::Serializers::JSON
13
+ model.send :include, ActiveModel::Serializers::Xml
14
+ end
15
+
16
+ def attributes=(attributes)
17
+ attributes.each do |k, v|
18
+ if k == k.plural
19
+ v = case v
20
+ when String
21
+ [v]
22
+ when Array
23
+ v
24
+ when Hash
25
+ v.values.flatten
26
+ end
27
+ end
28
+ send("#{k}=", v)
29
+ end
30
+ end
31
+
32
+ def attributes
33
+ map = instance_variables.collect do |name|
34
+ [name[1,1000], send(name[1,1000].to_sym)]
35
+ end.reject do |x|
36
+ x[1] == nil
37
+ end
38
+ Hash[map]
39
+ end
40
+ end
41
+ end
42
+
43
+ module Ixtlan
44
+ module Guard
45
+ module Models
46
+ class Maintenance
47
+
48
+ include Ixtlan::SerializableModel
49
+
50
+ attr_accessor :groups
51
+
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,95 @@
1
+ module Ixtlan
2
+ module Guard
3
+ module Models
4
+ class UserUpdateManager
5
+
6
+ def initialize(options)
7
+ @group_model = options[:group_model]
8
+ @user_id = options[:user_id].to_sym
9
+ @plural_group_name = options[:plural_group_name].to_sym
10
+ @group_id = options[:group_id].to_sym
11
+ @group_ids = "#{options[:group_id]}s"
12
+ end
13
+
14
+ def update_groups(user, params = [])
15
+ allowed_ids = user.current_user.all_groups.collect { |g| g.id.to_s }
16
+
17
+ group_ids = params[@group_ids] || []
18
+ group_ids = intersect(group_ids, allowed_ids)
19
+
20
+ current_ids = user.send(@plural_group_name).collect { |g| g.id.to_s }
21
+ current_ids = intersect(current_ids, allowed_ids)
22
+
23
+ # add
24
+ (group_ids - current_ids).each do |gid|
25
+ user.send(@plural_group_name) << @group_model.find(gid)
26
+ end
27
+
28
+ #delete
29
+ (current_ids - group_ids).each do |gid|
30
+ user.groups.delete(@group_model.find(gid))
31
+ end
32
+
33
+ user.save
34
+ end
35
+
36
+ def update(user, params = {}, options = {})
37
+ raise "no user" unless user
38
+ user.current_user = params.delete("current_user") || params.delete(:current_user) unless user.current_user
39
+ raise "'current_user' not set" unless user.current_user
40
+
41
+ flavor_id = options[:flavor_id].to_sym
42
+ flavor_ids = "#{options[:flavor_id]}s"
43
+ association_model = options[:association_model]
44
+ retrieve_flavors_method = options[:flavors_for_group].to_sym
45
+
46
+ allowed_ids = user.current_user.send(retrieve_flavors_method, @group_model.admin_group).collect {|i| i.id }
47
+ allowed_group_ids = user.current_user.all_groups.collect { |g| g.id.to_s }
48
+
49
+ group_ids = params[@group_ids] || []
50
+ group_ids = intersect(group_ids, allowed_group_ids)
51
+ group_ids.each do |gid|
52
+ g = @group_model.find(gid)
53
+
54
+ # calculate intersection of current and allowed
55
+ current_ids = user.send(retrieve_flavors_method, gid.to_i).collect { |d| d.id }
56
+ current_ids = intersect(current_ids, allowed_ids)
57
+
58
+ # calculate intersection of target and allowed
59
+ target_ids = ((params.delete(g.to_name) || {})[flavor_ids] || []).collect { |i| i.to_i }
60
+ target_ids = intersect(target_ids, allowed_ids)
61
+
62
+ # delete
63
+ (current_ids - target_ids).each do |id|
64
+ return false unless association_model.delete_all(["user_id=? and group_id=? and #{flavor_id}=?", user.id, gid, id])
65
+ end
66
+
67
+ # add
68
+ (target_ids - current_ids).each do |id|
69
+ return false unless association_model.create(@user_id => user.id, @group_id => gid, flavor_id => id)
70
+ end
71
+ end
72
+ true
73
+ end
74
+
75
+ def managed_flavors_for_group(user, group_or_id, options)
76
+ retrieve_flavors_method = options[:flavors_for_group].to_sym
77
+ group = @group_model.get(group_or_id)
78
+ if group.root?
79
+ []
80
+ else
81
+ existing = user.send(retrieve_flavors_method, group)
82
+ managed = user.send(retrieve_flavors_method, @group_model.admin_group)
83
+ intersect(managed, existing)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def intersect(set1, set2)
90
+ set1 - (set1 - set2)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,88 @@
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