stonewall 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 bokmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ = stonewall
2
+
3
+ Stonewall is a state-based access control language extracted from the StonePath Stateful Workflow Modeling Gem (which was itself an extraction on a couple of projects, which were based on an internal Java workflow tool dating back to ~1998). Long roots, but the ideas are being freshly re-evaluated in the context of an awesome language.
4
+
5
+ StoneWall is not meant to be stand-alone - it will likely make assumptions about ActiveRecord (or ActiveModel when I start thinking about Rails 3). It will be wedded into some rails concepts like controlers, etc.
6
+
7
+ == Why another ACL? Aren't there like, 30 of em?
8
+ StoneWall wants to be a complimentary dsl in the model that works alongside the StonePath DSL, allowing access control to be based on not just objects, users, and roles, but also on the state of the object being accessed... as in "A data entry clerk has read/write access while we are in the data entry state, read-only while we are in the data validation state, and no access at all in any other state."
9
+
10
+ Further, I have some opinions about where access control belongs. Access control is a business logic thing, and belongs in the model. How to deal with blocked access is a controller thing, and how to vary the display of data when your rights change is a view thing. Several acl projects out there act as if it belongs in the controller.
11
+
12
+ == What does it look like?
13
+ There was an earlier, functional version in StonePath gems prior to 0.3.0. This work will be based on that work, but I'm re-evaluating everything about that earlier implementation. The first attempt at an extraction wasn't 'opinionated' enough, and it suffered because of it.
14
+
15
+ == Note on Patches/Pull Requests
16
+
17
+ * Fork the project.
18
+ * Make your feature addition or bug fix.
19
+ * Add tests for it. This is important so I don't break it in a
20
+ future version unintentionally.
21
+ * Commit, do not mess with rakefile, version, or history.
22
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
23
+ * Send me a pull request. Bonus points for topic branches.
24
+
25
+ == Copyright
26
+
27
+ Copyright (c) 2010 bokmann. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "stonewall"
8
+ gem.summary = %Q{extracting the acl constructs from stonepath}
9
+ gem.description = %Q{The acl from StoneWall, now as a shiny new gem!}
10
+ gem.email = "dbock@codesherpas.com"
11
+ gem.homepage = "http://github.com/bokmann/stonewall"
12
+ gem.authors = ["bokmann"]
13
+ gem.add_dependency('activerecord','>= 2.0.0')
14
+ gem.add_dependency('sentient_user','>= 0.1.0')
15
+
16
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
38
+ rescue LoadError
39
+ task :rcov do
40
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+ end
43
+
44
+ task :test => :check_dependencies
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "stonewall #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/design_notes.txt ADDED
@@ -0,0 +1,427 @@
1
+ class Schpoo
2
+
3
+ stonewall do |acl|
4
+ #guarded methods are now deniedd unless specifically allowed.
5
+ acl.guard :method_name
6
+ acl.guard :group_name, [:method_names, :method2, :method3, :etc]
7
+
8
+ #multiple_role_policy :any | :all - this should actually be defined in an initializer, not the class
9
+
10
+ acl.access_for :assistant_manager do |role|
11
+ role.allow :all #magic that undoes all guards
12
+ role.allow :method_name
13
+ role.check :group_name, do |o, u| #pass in the object being guarded & the person requesting access
14
+ #return true or false
15
+ end
16
+ end
17
+
18
+ acl.authorization :verb do |user, object|
19
+ # return true/false
20
+ # can now say current_user.may_verb_schpoo?(my_schpoo) just like Aegis
21
+ # note that if you don't pass in a schpoo, you can check for class-level access. Thats up to you.
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+
28
+ # if someone calls a method on a model that they aren't allowed to access, it throws an AccessViolationException
29
+
30
+
31
+
32
+ # overly verbose, but nice. no exception, just true, false.
33
+ user.may_call(:method).on(object)
34
+ user.may_call_method_on_schpoo?(my_schpoo) #less practical
35
+ user.may_send(object, :method) # probably easiest to implement
36
+ user.may_auth_verb_schpoo(my_schpoo)
37
+
38
+
39
+ class Schpoo
40
+
41
+ stonewall do |acl|
42
+ #guarded methods are now deniedd unless specifically allowed.
43
+ acl.guard :method_name
44
+ acl.guard :group_name, [:method_names, :method2, :method3, :etc]
45
+
46
+ #multiple_role_policy :any | :all
47
+
48
+ acl.varies_on(:aasm_state) do |variant|
49
+ variant.value("created") do |acl|
50
+ acl.access_for :assistant_manager do |role|
51
+ role.allow :all #magic that undoes all guards
52
+ role.allow :method_name
53
+ role.check :group_name, do |o, u| #pass in the object being guarded & the person requesting access
54
+ #return true or false
55
+ end
56
+ end
57
+ end
58
+
59
+ variant.value("approved") do |acl|
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+
67
+
68
+ #This shows a variant that gets data out of the db.
69
+
70
+ class Schpoo
71
+
72
+ stonewall do |acl|
73
+
74
+ # This is the same dsl, but since it is just ruby code, you can mix it
75
+ # with ruby code that gets the data from a database.
76
+ # This code below is notional, but we coudl set it up as part of the
77
+ # whole framework if we want to get that complicated.
78
+
79
+ #acl.load_protected_groups_for(self.to_sym)
80
+ protected_groups = ProtectedGroups.find_by_class_name(self.to_sym)
81
+ protected_groups.each do |group|
82
+ acl.guard group.name.to_sym, group.methods
83
+ end
84
+
85
+ #acl.load_grants_policy_for(self.to_sym)
86
+ acl.varies_on(:aasm_state) do |variant|
87
+ states.each do |state|
88
+ variant.value(state) do |acl|
89
+ roles.each do |role|
90
+ acl.access_for role do |r|
91
+ allowed_methods = Grant.find_by_class_name_and_role(self.to_sym, role)
92
+ r.allow allowed_methods.collect{ |m| m.name.to_sym }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # from the db perspective, to keep things simple, we only support methods in
101
+ # groups. You can use a default group named "all" if you don't want to take
102
+ # avantage of groups.
103
+ #protected_groups_table # grouped_methods_table
104
+ #id name stonewall_id # protected_group_id method
105
+
106
+
107
+ #stonewalls_table
108
+ # class_name variant_attribute
109
+ #(the Stonewall object will also list all roles, methods, and method groups)
110
+
111
+ #grants_table
112
+ #stonewall_id variant role method method_group
113
+
114
+
115
+ class Schpoo
116
+ require stonewall
117
+
118
+ stonewall.load_from_db
119
+
120
+ stonewall do |acl|
121
+ acl.authorization :view do |user, object|
122
+ return ["administrator", "Developer", "Manager"].include?(user.role)
123
+ end
124
+ end
125
+ end
126
+
127
+ <% if current_user.may_view_schpoo? %>
128
+ <%# render link to view here %>
129
+ <% end %>
130
+
131
+ and use in your own before_filters to guard things like the show method.
132
+
133
+ Schpoo.reload_stonewall_policy
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+ class Schpoo
148
+
149
+ stonewall do |acl|
150
+ # tells the framework that if roles specify nothing for a guarded method,
151
+ # default answer is allow (this is the default setting)
152
+ check_default :allow
153
+
154
+ # tells the framework that if roles specify nothing for a guarded method,
155
+ # default answer is denied
156
+ check_default :deny
157
+
158
+
159
+ # tells the framework users have multiple roles to check, and
160
+ # if default mode is allow:
161
+ # any deny is a denial
162
+ # if default mode is deny:
163
+ # any allow is an allowal
164
+ multiple_role_policy :any
165
+
166
+ # tells the framework users have multiple roles to check, and
167
+ # if default mode is allow:
168
+ # all must deny for denial
169
+ # if default mode is deny:
170
+ # all must allow for allowal
171
+ multiple_role_policy :all #this is the default setting
172
+
173
+
174
+ # declare what you want to guard everything else is unchecked
175
+ acl.guard_method :save
176
+ acl.guard_method :name
177
+ acl.guard_method :name=
178
+ acl.guard_method :description
179
+ acl.guard_method :description=
180
+
181
+ #you can group them for easy reference below
182
+ acl.method_group :modifiers, [:save, :name=, :description=]
183
+ acl.method_group :accessors, [:name, :description]
184
+
185
+ acl.access_for :assistant_manager do |role_definition|
186
+ role_definition.deny :name= #use deny when :check_default is :allow
187
+ role_definition.allow :name= #use allow when :check_default is deny
188
+ role_Definition.check :name=, { |object, user|
189
+ # used when you want to make up your mind at runtime.
190
+ return false
191
+ }
192
+ end
193
+
194
+ acl.access_for :peon do |role_definition}
195
+ role_definition.field_variant(:aasm_state) do |state|
196
+ state.variant(:in_process) do |variant_definition|
197
+ variant_definition.deny :name=
198
+ variant_definition.allow :name=
199
+ variant_definition.check :name=, { |object, user, variant, value|
200
+ return false
201
+ }
202
+
203
+ state.variant(:catch_all) ...
204
+ end
205
+ end
206
+
207
+ end
208
+
209
+
210
+
211
+
212
+ we could even have code blocks for getting the role when given a user, etc.
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+ - access control is business logic and should be defined in the domain
224
+ - this is the kind of problem that can be specified with a terse internal dsl
225
+ - access is granular on methods on the instance or class level (on domain objects)
226
+ - the framework should stay out of the way unless I want it
227
+ - when I mark a method as guarded, it should be denied unless the access control specifically allows it.
228
+ - I will have a current_user attribute in my session
229
+ - my current_user will have either:
230
+ a role method that returns:
231
+ a string of the role name
232
+ or
233
+ an object that has a .name method that returns the role name
234
+ or
235
+ a roles method that returns a collection of objects that match the above description
236
+ - those role names will be used as part of the access control dsl
237
+ - if any of a users roles grants them access, they have access
238
+ - access control is governed by intersection of the users role and the method they are accessing
239
+ - optionally, access control is also governed by some state of the accessed object
240
+ - exceptions are reserved for exceptional circumstance
241
+ - models should throw exceptions if an access violation is attempted
242
+ - models should have a "can?" or 'may I?" methods that return true or false, so a prudent developer can avoid access exceptions
243
+ - controllers and views should have a terse method of checking access so that they can have logic flows to avoid exposing illegal operations.
244
+ - if the UI is designed well, an end user should not be able to generate access violation exceptions on the models
245
+ - the framework should give a minimal api to allow good ui design.
246
+
247
+ <% if current_user.is_allowed?(:access_to => "save", :on => this_complaint) %>
248
+
249
+ <% if current_user.may_print?(this_complaint) %>
250
+
251
+ <% if current_user.may_<authorization>?(this_complaint) %>
252
+
253
+
254
+ my_model.allows?(:method_symbol) #uses current user
255
+ my.model.allows_for_user?(:method_symbol, user)
256
+
257
+
258
+
259
+
260
+
261
+ # acl thoughts -
262
+ # I might want to pull acl out into its own gem.
263
+ # If I do, then the acl.for_state will get complicated.
264
+ # I might make it something that can be modeled like this:
265
+ #
266
+ # acl.for_field(:aasm_state).has_value("in_process") do
267
+ # end
268
+ #
269
+ # which would make this a *lot* more reusable in different
270
+ # contexts.
271
+
272
+ # -
273
+ # I think there is scizophrenia caused by the framework not
274
+ # committing to "deny unless access allowed" vs. "allow unless
275
+ # access denied". I think the principal of least surprise would
276
+ # dictate we allow unless specifically denied, and I'm almost
277
+ # ready to commit to that.
278
+
279
+ # -
280
+ # There are also issues when a use has one role vs. many roles.
281
+ # When there is one role, things are easy.
282
+ # When they have multiple roles, do we deny if _any_ of them are
283
+ # denied, or allow unless _all_ are denied? I think POLS would
284
+ # say allow unless all denied... but then, which return block do
285
+ # we return? Perhaps something simple like "the first one defined".
286
+ #
287
+ # -
288
+ # as its own gem though, stonepath acl would need a name... hmm...
289
+ # what do you call something near a stonepath that restricts access
290
+ # in certain directions? a stonewall!
291
+ #
292
+ # -
293
+ # defining things nested like state/role makes certain sense (as would
294
+ # role/state), but it makes answering some questions hard... for instance:
295
+ # "What are all the permissions that managers have? and "what are all the
296
+ # access_restrictions on the "in_process" state? You have to look across
297
+ # several tasks. PErhaps a rake task could help generate a report with
298
+ # this info for auditing purposes.
299
+ stonepath_acl do |acl|
300
+
301
+ acl.guard_method :save
302
+ acl.guard_method :name=
303
+ acl.guard_method :name
304
+ acl.guard_method :regarding=
305
+ acl.guard_method :regarding
306
+ acl.guard_method :description
307
+ acl.guard_method :description=
308
+ acl.method_group :modifiers, [:save, :name=, :regarding=, :description=]
309
+ acl.method_group :accessors, [:name, :regarding, :description]
310
+
311
+ acl.for_state :in_process do |state|
312
+ state.access_for_role :manager do |role|
313
+ role.allow_method :name #allow works if we are in deny first mode.
314
+ end
315
+
316
+ state.access_for_role :peon do |role|
317
+ role.deny_method :name=
318
+ role.deny_method :regarding=
319
+ role.deny_method(:description){ #value of block is returned. might this need to be a proc?
320
+ "#{user.name}, You do not have access to look at the description, you peon!"
321
+ }
322
+ end
323
+
324
+ state.access_for_role :assistant_manager do |role|
325
+ role.check_method_access :method_symbol do
326
+ # you can implement conditional logic here.
327
+ # maybe you want to defer the true/false
328
+ # access decision and check some attribute of
329
+ # the user.
330
+ return false #your conditional answer.
331
+ end
332
+ end
333
+ end
334
+
335
+ acl.for_state :completed do |state|
336
+ state.access_for_role :manager do |role|
337
+ role.deny_method_group :modifiers
338
+ end
339
+
340
+ state.access_for_role :peon do |role|
341
+ role.deny_method_group :modifiers
342
+ role.deny_method_group :accessors
343
+ end
344
+
345
+ end
346
+ end
347
+
348
+
349
+
350
+
351
+
352
+ in memory, the acl can be unrolled to a data structure like:
353
+ [method][role][pivot]
354
+ [method][role][pivot] = true | false
355
+
356
+
357
+
358
+ class Person < ActiveRecord::Base
359
+ include StoneWall
360
+
361
+ stonewall do |acl|
362
+ acl.guard :age
363
+ acl.guard :favorite_color
364
+ acl.method_group :privacy_factors, [:age, :favorite_color]
365
+ acl.varies_on :aasm_state
366
+
367
+ acl.action :edit
368
+ acl.action :print
369
+ acl.action :subscribe_to
370
+
371
+ role :manager do |manager|
372
+ manager.methods :privacy_factors
373
+ manager.actions [:edit, :print, :subscribe_to]
374
+ end
375
+
376
+ role :clerk do |clerk|
377
+ clerk.variant :data_entry do |data_entry|
378
+ data_entry.methods :age
379
+ data_entry.actions :edit
380
+ end
381
+ end
382
+ end
383
+
384
+ stonewall.permissions do |permissions|
385
+ permissions.add :edit |user, post| do
386
+ post.owner == user || user.is_an_admin?
387
+ end
388
+ end
389
+
390
+ end
391
+
392
+
393
+ class Person < ActiveRecord::Base
394
+ include StoneWall
395
+
396
+ stonewall do
397
+ varies_on :aasm_state
398
+ guard :age
399
+ guard :favorite_color
400
+ method_group :privacy_factors, [:age, :favorite_color]
401
+
402
+ role :manager do
403
+ allowed_methods :privacy_factors
404
+ end
405
+
406
+ role :clerk do
407
+ variant :data_entry do
408
+ allowed_method :age
409
+ end
410
+ end
411
+
412
+ action :edit do |person, user|
413
+ person.owner == user || user.is_administrator?
414
+ end
415
+
416
+ action :print do |person, user|
417
+ person.complete? && user.is_ogc_attorney?
418
+ end
419
+ end
420
+ end
421
+
422
+
423
+ user.may_edit_person? person
424
+
425
+
426
+
427
+
@@ -0,0 +1,58 @@
1
+ module StoneWall
2
+
3
+ # a word of caution, use this framework to guard 'normal user space' methods
4
+ # only - don't try to guard meta stuff like 'send'. If you have ever seen
5
+ # 'Being John Malkovich', you can understand why.
6
+ class AccessController
7
+ attr_reader :guarded_class
8
+ attr_reader :variant_field
9
+ attr_accessor :actions
10
+ attr_accessor :guarded_methods
11
+ attr_accessor :method_groups
12
+
13
+ # the matrix is the money-shot of the access controller. You can set it
14
+ # through add_grant, and query it through 'granted', but you can also
15
+ # access the data structure directly if you want to avoid the dsl.
16
+ # It is in the format [role][varient][member]
17
+ attr_accessor :matrix
18
+
19
+ def initialize(guarded_class)
20
+ @guarded_class = guarded_class
21
+ @guarded_methods = Array.new
22
+ @actions = Hash.new
23
+ @matrix = Hash.new
24
+ @method_groups = Hash.new
25
+ end
26
+
27
+ def set_variant_field(field)
28
+ if @variant_field.nil?
29
+ @variant_field = field
30
+ else
31
+ raise "no redefinition of variant field"
32
+ end
33
+ end
34
+
35
+ def add_grant(r, v, m)
36
+ @matrix[r] ||= Hash.new
37
+ @matrix[r][v] ||= Array.new
38
+ @matrix[r][v] << m
39
+ end
40
+
41
+ def granted?(r, v, m)
42
+ matrix[r] && matrix[r][v] && matrix[r][v].include?(m)
43
+ end
44
+
45
+ # --------------
46
+ # This is 1/3rd of the magic in this gem. Every method you guard is
47
+ # checked by this method. It looks at the matrix of permissions you built
48
+ # in the dsl and allows or denies based on the guarded object, the user,
49
+ # and the method being accessed. #should we fail secure?
50
+ def allowed?(guarded_object, user, method)
51
+ return true if (guarded_object.nil? || user.nil? || method.nil?)
52
+ v = variant_field ? guarded_object.send(variant_field).to_sym : :all
53
+ user.stonepath_role_info.detect do |r|
54
+ granted?(r, v, method)
55
+ end || false
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ module StoneWall
2
+ module Helpers
3
+
4
+ def self.symbolize_role(role)
5
+ [String, Symbol].include?(role.class) ? role.to_sym : role.name.to_sym
6
+ end
7
+
8
+ # --------------
9
+ # This is 1/3rd of the magic in this gem. We earlier built a
10
+ # 'schpoo_with_stonepath' method on your class, and now we use
11
+ # alias_method_chain to wrap your original 'schpoo' method.
12
+ # You will have no hope of understanding this if you don't understand
13
+ # alias_method_chain, so go memorize that documentation.
14
+ # We have to do this after ActoveRecord synthesizes the attribute methods
15
+ # with a call to 'define_attribute_methods'; you'll see some magic in the
16
+ # base.instance_eval in the other file to make that magic happen.
17
+ def self.fix_aliases_for(guarded_class)
18
+ guarded_class.stonewall.guarded_methods.each do |m|
19
+ guarded_class.send(:alias_method_chain, m, :stonewall)
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module StoneWall
2
+ class Parser
3
+ def initialize(parent, role = :all, variant = :all)
4
+ @parent, @role, @variant = parent, role, variant
5
+ @method_groups = Hash.new
6
+ end
7
+
8
+ def allowed_method(method)
9
+ @parent.stonewall.add_grant(@role, @variant, method)
10
+ end
11
+
12
+ def allowed_methods(method_names)
13
+ @parent.stonewall.method_groups[method_names].each do |m|
14
+ allowed_method m
15
+ end
16
+ end
17
+
18
+ def method_group(name, methods)
19
+ @parent.stonewall.method_groups[name] = methods
20
+ end
21
+
22
+ def varies_on(field)
23
+ @parent.stonewall.set_variant_field(field)
24
+ end
25
+
26
+ def action(action_name, &guard)
27
+ @parent.stonewall.actions[action_name] = guard
28
+ end
29
+
30
+ def role(role_name)
31
+ yield Parser.new(@parent, role_name)
32
+ end
33
+
34
+ def variant(variant_name)
35
+ yield Parser.new(@parent, @role, variant_name)
36
+ end
37
+
38
+ def guard(method)
39
+ @parent.stonewall.guarded_methods << method
40
+ aliased_target, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
41
+ checked_method = "#{aliased_target}_with_stonewall#{punctuation}"
42
+ unchecked_method = "#{aliased_target}_without_stonewall#{punctuation}"
43
+ # --------------
44
+ # This method is defined on the guarded class, so it is callable on
45
+ # objects of that class. This is 1/3rd of the magic of this gem-
46
+ # if you declare 'schpoo' a guarded method, we generate this
47
+ # 'schpoo_with_stonewall' method. Elsewhere, we use alias_method_chain
48
+ # to wrap your original 'schpoo' method.
49
+ @parent.send(:define_method, checked_method) do |*args|
50
+ if stonewall.allowed?(self, User.current, method)
51
+ self.send(unchecked_method, *args)
52
+ else
53
+ raise "Access Violation"
54
+ end
55
+ end
56
+ # -------------- end of bizzaro meta-juju
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ module StoneWall
2
+ def self.included(base)
3
+ base.instance_eval do
4
+ def stonewall()
5
+ require File.expand_path(File.dirname(__FILE__)) +
6
+ "/access_controller.rb"
7
+ require File.expand_path(File.dirname(__FILE__)) +
8
+ "/parser.rb"
9
+ require File.expand_path(File.dirname(__FILE__)) +
10
+ "/helpers.rb"
11
+ require File.expand_path(File.dirname(__FILE__)) +
12
+ "/user_extensions.rb"
13
+ cattr_accessor :stonewall
14
+ self.stonewall = StoneWall::AccessController.new(self)
15
+ yield StoneWall::Parser.new(self)
16
+ end
17
+
18
+ # --------------
19
+ # This is 1/3rd of the magic in this gem. After ActiceRecord defines
20
+ # the classes attribute methods, we come along and alias all of the
21
+ # guarded methods defined in the dsl in the class.
22
+ def self.define_attribute_methods_with_stonewall
23
+ define_attribute_methods_without_stonewall
24
+ StoneWall::Helpers.fix_aliases_for(self)
25
+ end
26
+
27
+ class << self
28
+ alias_method_chain :define_attribute_methods, :stonewall
29
+ end
30
+ end
31
+
32
+ # mimicking the send method, we want to ask permission first with send?
33
+ def send?(method, user = User.current)
34
+ self.class.stonewall.allowed?(self, user, method)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ module StoneWall
2
+ module UserExtensions
3
+ require File.expand_path(File.dirname(__FILE__)) + "/helpers.rb"
4
+
5
+ # This is the ugliest method in the whole gem.
6
+ # You have a lot of freedom in how you implement your role system, and
7
+ # I have to adapt to that. You can have:
8
+ # - role as a string on your class
9
+ # - role as a singular has_one or belongs_to relationship
10
+ # - your user can has_many :roles, and they can be any object you want.
11
+ # The only thing we require is that, if your role is a separate object,
12
+ # it responds to a 'name' method with a string or a symbol that matches
13
+ # the symbol you use when defining the permissions in your dsl.
14
+ def stonepath_role_info
15
+ return @role_symbols if @role_symbols
16
+ @role_symbols = Array.new
17
+ if self.respond_to?(:role)
18
+ @role_symbols << StoneWall::Helpers.symbolize_role(role)
19
+ end
20
+
21
+ if self.respond_to?(:roles)
22
+ roles.each do |role|
23
+ @role_symbols << StoneWall::Helpers.symbolize_role(role)
24
+ end
25
+ end
26
+ @role_symbols
27
+ end
28
+
29
+ # I like Aegis so much, I stole their idea for this part of the gem. I
30
+ # hope you don't mind guys, but I really need an ACL that triggers off
31
+ # of object state!
32
+ def may_send?(object, method)
33
+ object.class.stonewall.allowed?(object, self, method)
34
+ end
35
+
36
+ def method_missing_with_stonewall(symb, *args)
37
+ method_name = symb.to_s
38
+ if method_name =~ /^may_(.+?)[\!\?]$/
39
+ args.first.class.stonewall.actions[$1.to_sym].call(args.first, self)
40
+ else
41
+ method_missing_without_stonewall(symb, *args)
42
+ end
43
+ end
44
+
45
+ alias_method_chain :method_missing, :stonewall
46
+
47
+ end
48
+ end
data/lib/stonewall.rb ADDED
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) ||
3
+ $:.include?(File.expand_path(File.dirname(__FILE__)))
4
+
5
+ require File.expand_path(File.dirname(__FILE__)) + "/stonewall/stonewall.rb"
6
+ require File.expand_path(File.dirname(__FILE__)) + "/stonewall/user_extensions.rb"
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'stonewall'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestStonewall < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stonewall
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - bokmann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-20 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sentient_user
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: thoughtbot-shoulda
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: The acl from StoneWall, now as a shiny new gem!
46
+ email: dbock@codesherpas.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - .document
56
+ - .gitignore
57
+ - LICENSE
58
+ - README.rdoc
59
+ - Rakefile
60
+ - VERSION
61
+ - design_notes.txt
62
+ - lib/stonewall.rb
63
+ - lib/stonewall/access_controller.rb
64
+ - lib/stonewall/helpers.rb
65
+ - lib/stonewall/parser.rb
66
+ - lib/stonewall/stonewall.rb
67
+ - lib/stonewall/user_extensions.rb
68
+ - test/helper.rb
69
+ - test/test_stonewall.rb
70
+ has_rdoc: true
71
+ homepage: http://github.com/bokmann/stonewall
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --charset=UTF-8
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ version:
91
+ requirements: []
92
+
93
+ rubyforge_project:
94
+ rubygems_version: 1.3.5
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: extracting the acl constructs from stonepath
98
+ test_files:
99
+ - test/helper.rb
100
+ - test/test_stonewall.rb