ixtlan-guard 0.1.0 → 0.4.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.
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