mikldt-authenticates_access 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ log/*.log
2
+ tmp/**/*
3
+ db/*.sqlite3
4
+ doc/api
5
+ doc/app
6
+ vendor
7
+ .*.swp
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Andrew H. Armenia
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 ADDED
@@ -0,0 +1,86 @@
1
+ AuthenticatesAccess
2
+ ===================
3
+
4
+ AuthenticatesAccess can be used to implement model-based authentication and
5
+ authorization features in your application. It is based around the concept
6
+ of "accessors", or model objects which are used as tokens to access other
7
+ model objects. Accessors might be users, groups, or sessions.
8
+ AuthenticatesAccess allows the use of methods within the accessors or within
9
+ the accessed objects to determine whether certain actions should be allowed.
10
+
11
+ Example
12
+ =======
13
+
14
+ Models need to define the access restrictions which will apply. If the concept
15
+ of "ownership" is to be used, it is necessary to define which attribute
16
+ refers to the object's owner. The owner should fill the role of accessor
17
+ in the application.
18
+
19
+ class User < ActiveRecord::Base
20
+ # user has an is_admin attribute
21
+
22
+ # don't let non-admins change the is_admin attribute
23
+ authenticates_writes_to :is_admin, :with_accessor_method => :is_admin
24
+
25
+ # allow users to save their own profile
26
+ authenticates_saves :with => :allow_owner
27
+
28
+ # allow admins to save the profile as well
29
+ authenticates_saves :with_accessor_method => :is_admin
30
+
31
+ # note that ownership doesn't confer all privileges!
32
+ # has_owner :self means that the accessor's ID will be compared
33
+ # with this object's own ID for the allow_owner test.
34
+ has_owner :self
35
+
36
+ # also, allow admins to save any user profile
37
+ authenticates_saves :with_accessor_method => :is_admin
38
+ end
39
+
40
+ class Comment < ActiveRecord::Base
41
+ belongs_to :user
42
+
43
+ # allow users to edit their own comments (but not others)
44
+
45
+ # has_owner :user means that user.id will be compared to accessor.id
46
+ # for the allow_owner test to pass.
47
+ has_owner :user
48
+
49
+ # register the ownership test for any saves
50
+ authenticates_saves :with => :allow_owner
51
+
52
+ # this will also allow admins to edit any comments
53
+ authenticates_saves :with_accessor_method => :is_admin
54
+
55
+ # this makes the creating user the owner of the comment
56
+ autosets_owner_on_create
57
+ end
58
+
59
+ The application controller should set an accessor to be used:
60
+
61
+ class ApplicationController < ActionController::Base
62
+ before_filter :setup_accessor
63
+
64
+ protected
65
+
66
+ def setup_accessor
67
+ ActiveRecord::Base.accessor = logged_in_user
68
+ end
69
+
70
+ def logged_in_user
71
+ User.find(session[:user_id])
72
+ end
73
+ end
74
+
75
+ The views may use methods to determine which attributes may currently
76
+ be written, or whether the object may be modified at all.
77
+
78
+ <% if @user.allowed_to_save(:is_admin) %>
79
+ <%= f.check_box :is_admin %>
80
+ <% end %>
81
+
82
+ <% if user.allowed_to_save %>
83
+ <%= link_to 'Edit', edit_user_path(user) %>
84
+ <% end %>
85
+
86
+ Copyright (c) 2009 Andrew H. Armenia, released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the authenticates_access plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the authenticates_access plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'AuthenticatesAccess'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "mikldt-authenticates_access"
29
+ gemspec.summary = "Model-based Authorization on Rails!"
30
+ gemspec.description = "Model-based read and write user authorization in Rails"
31
+ gemspec.email = "mikldt@gmail.com"
32
+ gemspec.homepage = "http://github.com/mikldt/authenticates_access"
33
+ gemspec.authors = ["Michael DiTore"]
34
+ end
35
+ Jeweler::GemcutterTasks.new
36
+ rescue LoadError
37
+ puts "Jeweler not available. Install it with: gem install jeweler"
38
+ end
39
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ # Install our class and instance methods into ActiveRecord
2
+ RAILS_DEFAULT_LOGGER.error("Loading AuthenticatesAccess...")
3
+
4
+ ActiveRecord::Base.class_eval do
5
+ extend AuthenticatesAccess::ClassMethods
6
+ end
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,440 @@
1
+ # AuthenticatesAccess
2
+
3
+ module AuthenticatesAccess
4
+ class AuthMethod < Struct.new(:type, :name, :options)
5
+ end
6
+
7
+ class AuthMethodList < Array
8
+ def add_method(options)
9
+ if options[:with_accessor_method]
10
+ self << AuthMethod.new( :accessor, options[:with_accessor_method], options )
11
+ elsif options[:with]
12
+ self << AuthMethod.new( :model, options[:with], options )
13
+ else
14
+ fail "Either :with or :with_accessor_method must be specified"
15
+ end
16
+ end
17
+
18
+ def check(targets)
19
+ # start out assuming we have not passed any tests
20
+ # if one passes this gets set to true
21
+ passed = false
22
+
23
+ self.each do |method|
24
+ unless targets[method.type].nil?
25
+ target = targets[method.type]
26
+ if run_method(target, method.name.to_sym, method.options[:options])
27
+ passed = true
28
+ end
29
+ end
30
+ end
31
+ passed
32
+ end
33
+
34
+ protected
35
+
36
+ # Run a method on the object if it's available, otherwise return false.
37
+ def run_method(object, method, options)
38
+ if object.nil?
39
+ false
40
+ elsif object.respond_to?(method)
41
+ if options
42
+ object.send(method, options)
43
+ else
44
+ object.send(method)
45
+ end
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+
54
+ module ClassMethods
55
+ # Set an accessor to be used
56
+ def accessor=(accessor)
57
+ @@accessor = accessor
58
+ end
59
+
60
+ # Return the accessor being used by the model classes
61
+ def accessor
62
+ @@accessor ||= nil
63
+ @@accessor
64
+ end
65
+
66
+ # Include the instance methods used to implement authentication
67
+ def authenticates_access
68
+ include InstanceMethods
69
+ end
70
+
71
+ # Used to require an authentication test to be passed on the accessor
72
+ # before the model may be saved or destroyed. If the test fails, an exception
73
+ # will be thrown. Multiple calls build a chain of tests. If any test
74
+ # passes, the accessor is considered authenticated. Attribute writes will
75
+ # also be disallowed if the object may not be saved by the accessor
76
+ #
77
+ # examples:
78
+ #
79
+ # authenticates_saves :with_accessor_method => :is_admin
80
+ # will only allow the object to be saved if the accessor's is_admin
81
+ # method returns true
82
+ #
83
+ # authenticates_saves :with => :allow_owner
84
+ # will only allow the object to be saved if its own allow_owner
85
+ # method returns true
86
+ #
87
+ def authenticates_saves(options={})
88
+ unless @save_method_list
89
+ authenticates_access
90
+ before_save :auth_save_filter
91
+ before_destroy :auth_save_filter
92
+ @save_method_list = AuthMethodList.new
93
+ end
94
+
95
+ @save_method_list.add_method(options)
96
+ end
97
+
98
+ # Used to require that an authentication test is passed on the accessor
99
+ # before data may be read from the model.
100
+ def authenticates_reads(options={})
101
+ unless @read_method_list
102
+ authenticates_access
103
+ #Sadly, no easy way to block reads at this level
104
+ @read_method_list = AuthMethodList.new
105
+ end
106
+
107
+ @read_method_list.add_method(options)
108
+ end
109
+
110
+
111
+ # Used to specify that a given attribute should only be written to if the
112
+ # accessor passes a test. The test may be a method of the accessor or
113
+ # of the object itself, which should return a boolean value.. If the test
114
+ # fails, the attribute write will be ignored. Multiple calls build up
115
+ # a chain of tests: if any test in the chain passes, the accessor is
116
+ # considered authorized.
117
+ #
118
+ # Examples:
119
+ #
120
+ # authenticates_writes_to :is_admin, :with_accessor_method => :is_admin
121
+ # would only allow admins to grant or revoke the admin privileges of others
122
+ #
123
+ # authenticates_writes_to :title, :with => :allow_owner
124
+ # would only allow the owner of this object to edit its title
125
+ #
126
+ def authenticates_writes_to(attr, options={})
127
+ authenticates_access
128
+ @write_validation_map ||= {}
129
+ @write_validation_map[attr.to_s] ||= AuthMethodList.new
130
+ @write_validation_map[attr.to_s].add_method(options)
131
+ end
132
+
133
+ # Used to specify that a given attribute may only be read if the
134
+ # accessor passes a test. Behaves similarly to authenticates_writes_to
135
+ def authenticates_reads_from(attr, options={})
136
+ authenticates_access
137
+ @read_validation_map ||= {}
138
+ @read_validation_map[attr.to_s] ||= AuthMethodList.new
139
+ @read_validation_map[attr.to_s].add_method(options)
140
+ end
141
+
142
+ # You might use this one to only allow authenticated users to create objects
143
+ def authenticates_creation(options={})
144
+ unless @create_method_list
145
+ authenticates_access
146
+ before_create :auth_create_filter
147
+ @create_method_list = AuthMethodList.new
148
+ extend CreationControl
149
+ end
150
+
151
+ @create_method_list.add_method(options)
152
+ end
153
+
154
+ # Used to specify that a given attribute represents an accessor that is
155
+ # an owner of this object. This creates an allow_owner method which
156
+ # may be used to allow the object's owner to save the object, or edit
157
+ # certain attributes, as well as an owner_id method which returns the
158
+ # owner's ID. Currently, accessors are compared by their ID in the database,
159
+ # so accessors should be of the same class to avoid security holes.
160
+ # (i.e. if both User and Group are used as accessors, a User with ID 7 can
161
+ # access objects owned by a Group with ID 7).
162
+ # This may be fixed in the future.
163
+ #
164
+ # Example:
165
+ # belongs_to :user
166
+ # has_owner :user
167
+ # authenticates_saves :with => :allow_owner
168
+ #
169
+ # or:
170
+ #
171
+ # has_owner
172
+ # def owner_id
173
+ # id
174
+ # end
175
+ #
176
+ def has_owner(attr=nil)
177
+ unless attr.nil?
178
+ id_accessor = "#{attr.to_s}_id"
179
+ id_mutator = "#{attr.to_s}_id="
180
+ if attr == :self
181
+ # special case the self attribute but don't allow ownership change
182
+ define_method(:owner_id) do
183
+ id
184
+ end
185
+ else
186
+ define_method(:owner) do
187
+ self.send(attr)
188
+ end
189
+ define_method(:owner_id) do
190
+ self.send(id_accessor)
191
+ end
192
+ define_method("owner_id=") do |new_value|
193
+ self.send(id_mutator,new_value)
194
+ end
195
+ end
196
+ end
197
+ include Ownership
198
+ end
199
+
200
+ # If declared, the accessor used to create this object automatically
201
+ # becomes its owner.
202
+ #
203
+ # Examples:
204
+ #
205
+ # class Comment < ActiveRecord::Base
206
+ # belongs_to :member
207
+ # has_owner :member
208
+ # autosets_owner_on_create
209
+ # authenticates_saves :with => :allow_owner
210
+ # end
211
+ #
212
+ def autosets_owner_on_create
213
+ has_owner # this will do nothing if the user has already set up has_owner :something
214
+ # the hook runs before validation so we can validate_associated
215
+ before_validation_on_create :autoset_owner
216
+ end
217
+
218
+
219
+ def authenticates_saves_method_list
220
+ @save_method_list ||= nil
221
+ @save_method_list
222
+ end
223
+
224
+ def authenticates_reads_method_list
225
+ @read_method_list ||= nil
226
+ @read_method_list
227
+ end
228
+
229
+ def write_validations(attr)
230
+ @write_validation_map ||= nil
231
+ if @write_validation_map
232
+ @write_validation_map[attr]
233
+ else
234
+ nil
235
+ end
236
+ end
237
+
238
+ def read_validations(attr)
239
+ @read_validation_map ||= nil
240
+ if @read_validation_map
241
+ @read_validation_map[attr]
242
+ else
243
+ nil
244
+ end
245
+ end
246
+
247
+ end
248
+
249
+ module InstanceMethods
250
+ # Shorthand to get at the accessor of interest
251
+ def accessor
252
+ self.class.accessor
253
+ end
254
+
255
+ def bypass_auth
256
+ @bypass_auth = true
257
+ yield
258
+ @bypass_auth = false
259
+ end
260
+
261
+ # Auto-set the owner id to the accessor id before save if the object is new
262
+ def autoset_owner
263
+ bypass_auth do
264
+ if accessor
265
+ self.owner_id ||= accessor.id
266
+ end
267
+ end
268
+
269
+ true # this is very important!
270
+ end
271
+
272
+
273
+ # before_save/before_destroy hook installed by authenticates_saves
274
+ def auth_save_filter
275
+ if not allowed_to_save
276
+ # An interesting thought: could this throw an HTTP error?
277
+ false
278
+ else
279
+ true
280
+ end
281
+ end
282
+
283
+ # before_save/before_destroy hook installed by authenticates_saves
284
+ def auth_create_filter
285
+ if not self.class.allowed_to_create
286
+ false
287
+ else
288
+ true
289
+ end
290
+ end
291
+ # Included for completeness, this could be used to filter out accessors
292
+ # who can't read an object. Sadly, there's no way to install this, yet.
293
+ def auth_read_filter
294
+ if not allowed_to_read
295
+ false
296
+ else
297
+ true
298
+ end
299
+ end
300
+
301
+ # This method may be used to determine whether the current accessor has
302
+ # privileges to save the object. Returns true if so, false otherwise.
303
+ def allowed_to_save
304
+ method_list = self.class.authenticates_saves_method_list
305
+ if method_list.nil?
306
+ # No method list, so it's allowed
307
+ true
308
+ elsif method_list.check :accessor => accessor, :model => self
309
+ # Method list passed, so allowed
310
+ true
311
+ else
312
+ # Method list failed, so denied
313
+ false
314
+ end
315
+ end
316
+
317
+ # This method may be used to determine whether the current accessor has
318
+ # privileges to read data from the object. Returns true if so, else false.
319
+ def allowed_to_read
320
+ method_list = self.class.authenticates_reads_method_list
321
+ if method_list.nil?
322
+ # No method list, so it's allowed
323
+ true
324
+ elsif method_list.check :accessor => accessor, :model => self
325
+ # Method list passed, so allowed
326
+ true
327
+ else
328
+ # Method list failed, so denied
329
+ false
330
+ end
331
+ end
332
+
333
+ # Overload of write_attribute to implement the filtration
334
+ def write_attribute(name, value)
335
+ # Simply check if the accessor is allowed to write the field
336
+ # (if so, go to superclass and do it)
337
+ @bypass_auth ||= false
338
+ if allowed_to_write(name) || @bypass_auth
339
+ super(name, value)
340
+ end
341
+ end
342
+
343
+ # Overload of read_attribute to filter data access
344
+ def read_attribute(name)
345
+ @bypass_auth ||= false
346
+ if allowed_to_read_from(name) || @bypass_auth
347
+ super(name)
348
+ end
349
+ end
350
+
351
+ # This method may be used to determine if the current accessor may write
352
+ # to a given attribute. Returns true if so, false otherwise.
353
+ def allowed_to_write(name)
354
+ # no point allowing attribute writes if we can't save them?
355
+ if allowed_to_save
356
+ name = name.to_s
357
+ validation_methods = self.class.write_validations(name)
358
+ if validation_methods.nil?
359
+ # We haven't registered any filters on this attribute, so allow the write.
360
+ true
361
+ elsif validation_methods.check :accessor => accessor, :model => self
362
+ # One of the authentication methods worked, so allow the write.
363
+ true
364
+ else
365
+ # We had filters but none of them passed. Disallow write.
366
+ false
367
+ end
368
+ else
369
+ false
370
+ end
371
+ end
372
+
373
+ # This method may be used to determine if the current accessor may read
374
+ # a given attribute. Returns true if so, false otherwise.
375
+ def allowed_to_read_from(name)
376
+ # no point allowing attribute writes if we can't save them?
377
+ if allowed_to_read
378
+ name = name.to_s
379
+ validation_methods = self.class.read_validations(name)
380
+ if validation_methods.nil?
381
+ # We haven't registered any filters on this attribute, so allow the write.
382
+ true
383
+ elsif validation_methods.check :accessor => accessor, :model => self
384
+ # One of the authentication methods worked, so allow the write.
385
+ true
386
+ else
387
+ # We had filters but none of them passed. Disallow write.
388
+ false
389
+ end
390
+ else
391
+ false
392
+ end
393
+ end
394
+
395
+
396
+ # This method may be used to determine if the current accessor may write
397
+ # to a given attribute. Returns true if so, false otherwise.
398
+ # for now, if you can save, you can destroy
399
+ def allowed_to_destroy
400
+ allowed_to_save
401
+ end
402
+ end
403
+
404
+ module Ownership
405
+ # This method implements a simple test: whether the object is owned by
406
+ # the accessor. See has_owner in ClassMethods. Note that new records,
407
+ # with no owner, will always pass this test. This allows the
408
+ # (not-yet-existent) owner (i.e. the creator) to write any attributes
409
+ # that they would have had access to anyway. Use authenticates_creation
410
+ # to allow only certain accessors the right to create objects.
411
+ def allow_owner(options={})
412
+ if new_record?
413
+ true
414
+ elsif accessor.nil?
415
+ false # maybe this is failing up the works?
416
+ else
417
+ # must define an owner_id method for this to work
418
+ accessor.id == owner_id
419
+ end
420
+ end
421
+ end
422
+
423
+ module CreationControl
424
+ def allowed_to_create
425
+ method_list = @create_method_list
426
+ if method_list.nil?
427
+ # No method list, so it's allowed
428
+ true
429
+ elsif method_list.check :accessor => accessor
430
+ # Method list passed, so allowed
431
+ true
432
+ else
433
+ # Method list failed, so denied
434
+ false
435
+ end
436
+ end
437
+ end
438
+
439
+ end
440
+
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mikldt-authenticates_access}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael DiTore"]
12
+ s.date = %q{2010-01-24}
13
+ s.description = %q{Model-based read and write user authorization in Rails}
14
+ s.email = %q{mikldt@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "init.rb",
25
+ "install.rb",
26
+ "lib/authenticates_access.rb",
27
+ "mikldt-authenticates_access.gemspec",
28
+ "tasks/authenticates_access_tasks.rake",
29
+ "test/admin_item.rb",
30
+ "test/authenticates_access_test.rb",
31
+ "test/database.yml",
32
+ "test/fixtures/admin_items.yml",
33
+ "test/fixtures/owned_items.yml",
34
+ "test/fixtures/users.yml",
35
+ "test/owned_item.rb",
36
+ "test/test_helper.rb",
37
+ "test/user.rb",
38
+ "uninstall.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/mikldt/authenticates_access}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.5}
44
+ s.summary = %q{Model-based Authorization on Rails!}
45
+ s.test_files = [
46
+ "test/authenticates_access_test.rb",
47
+ "test/user.rb",
48
+ "test/test_helper.rb",
49
+ "test/owned_item.rb",
50
+ "test/admin_item.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ else
59
+ end
60
+ else
61
+ end
62
+ end
63
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :authenticates_access do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,3 @@
1
+ class AdminItem < ActiveRecord::Base
2
+ authenticates_saves :with_accessor_method => :is_admin
3
+ end
@@ -0,0 +1,108 @@
1
+ require 'test_helper'
2
+
3
+ class AuthenticatesAccessTest < ActiveSupport::TestCase
4
+ test "authenticates_saves :with => :allow_owner positive" do
5
+ ActiveRecord::Base.accessor = users(:user1)
6
+ item = owned_items(:item3)
7
+ item.description = "I changed the text"
8
+ assert item.save
9
+ end
10
+
11
+ test "authenticates_saves :with => :allow_owner negative" do
12
+ ActiveRecord::Base.accessor = users(:user1)
13
+ item = owned_items(:item4)
14
+ item.description = "I changed the text"
15
+ flunk if item.save
16
+ end
17
+
18
+ test "authenticates_saves :with => :allow_owner on new object" do
19
+ ActiveRecord::Base.accessor = users(:user1)
20
+ item = OwnedItem.new(:description => "This should work")
21
+ assert item.save
22
+ assert item.user_id == users(:user1).id
23
+ end
24
+
25
+ test "authenticates_creates negative" do
26
+ ActiveRecord::Base.accessor = users(:user1)
27
+ user = User.new(
28
+ :is_admin => true,
29
+ :name => "cracker",
30
+ :bio => "I like breaking into things"
31
+ )
32
+ flunk if user.save
33
+ end
34
+
35
+ test "authenticates_creates positive" do
36
+ ActiveRecord::Base.accessor = users(:admin)
37
+ user = User.new(
38
+ :is_admin => false,
39
+ :name => "some legit user",
40
+ :bio => "This should work"
41
+ )
42
+ assert user.save
43
+ end
44
+
45
+ test "authenticates_writes_to negative" do
46
+ ActiveRecord::Base.accessor = users(:user1)
47
+ user2 = users(:user2)
48
+ user2.is_admin = true
49
+ flunk if user2.is_admin
50
+ end
51
+
52
+ test "authenticates_writes_to positive" do
53
+ ActiveRecord::Base.accessor = users(:admin)
54
+ user2 = users(:user2)
55
+ user2.is_admin = true
56
+ assert user2.is_admin
57
+ end
58
+
59
+ test "allowed_to_create positive" do
60
+ ActiveRecord::Base.accessor = users(:admin)
61
+ assert User.allowed_to_create
62
+ end
63
+
64
+ test "allowed_to_create negative" do
65
+ ActiveRecord::Base.accessor = users(:user1)
66
+ flunk if User.allowed_to_create
67
+ end
68
+
69
+ test "allowed_to_write positive" do
70
+ ActiveRecord::Base.accessor = users(:admin)
71
+ assert users(:user1).allowed_to_write(:is_admin)
72
+ assert users(:user1).allowed_to_write(:bio)
73
+
74
+ ActiveRecord::Base.accessor = users(:user1)
75
+ assert users(:user1).allowed_to_write(:bio)
76
+
77
+ flunk if users(:user2).allowed_to_write(:bio)
78
+ end
79
+
80
+ test "allowed_to_write negative" do
81
+ ActiveRecord::Base.accessor = users(:user1)
82
+ flunk if users(:user1).allowed_to_write(:is_admin)
83
+ end
84
+
85
+ test "allowed_to_save as user1" do
86
+ ActiveRecord::Base.accessor = users(:user1)
87
+ assert owned_items(:item3).allowed_to_save
88
+ flunk if owned_items(:item4).allowed_to_save
89
+ end
90
+
91
+ test "can_create_but_not_change_owner" do
92
+ ActiveRecord::Base.accessor = users(:user1)
93
+ flunk if ActiveRecord::Base.accessor.is_admin
94
+ created = CreatedItem.new(:description => "something")
95
+ assert created.save
96
+ assert created.owner_id == users(:user1).id
97
+ created.owner_id = 1234
98
+ assert created.owner_id == users(:user1).id
99
+ end
100
+
101
+ test "admin_can_set_owner" do
102
+ ActiveRecord::Base.accessor = users(:admin)
103
+ created = CreatedItem.new(:description => "something")
104
+ created.owner_id = users(:user1).id
105
+ assert created.save
106
+ assert created.owner_id == users(:user1).id
107
+ end
108
+ end
data/test/database.yml ADDED
@@ -0,0 +1,5 @@
1
+ sqlite3:
2
+ database: ":memory:"
3
+ adapter: sqlite3
4
+ timeout: 500
5
+
@@ -0,0 +1,3 @@
1
+ item1:
2
+ description: "This text should not change"
3
+
@@ -0,0 +1,16 @@
1
+ item1:
2
+ user_id: 1
3
+ description: "I'm owned by the admin"
4
+
5
+ item2:
6
+ user_id: 2
7
+ description: "I'm owned by the other admin"
8
+
9
+ item3:
10
+ user_id: 3
11
+ description: "I'm owned by user1"
12
+
13
+ item4:
14
+ user_id: 4
15
+ description: "I'm owned by user2"
16
+
@@ -0,0 +1,23 @@
1
+ admin:
2
+ id: 1
3
+ name: The Administrator
4
+ is_admin: true
5
+ bio: I like writing code and doing other things
6
+
7
+ admin2:
8
+ id: 2
9
+ name: Another Administrator
10
+ is_admin: true
11
+ bio: I am also an administrator
12
+
13
+ user1:
14
+ id: 3
15
+ name: User 1
16
+ is_admin: false
17
+ bio: I own some things but not others
18
+
19
+ user2:
20
+ id: 4
21
+ name: User 2
22
+ is_admin: false
23
+ bio: I am like user 1
@@ -0,0 +1,7 @@
1
+ class OwnedItem < ActiveRecord::Base
2
+ belongs_to :user
3
+ has_owner :user
4
+ autosets_owner_on_create
5
+
6
+ authenticates_saves :with => :allow_owner
7
+ end
@@ -0,0 +1,75 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/test_case'
4
+
5
+ # workaround for rails being on crack (maybe?)
6
+ require 'test/unit'
7
+ require 'test/unit/testcase'
8
+ require 'active_support/testing/setup_and_teardown'
9
+ #require 'active_support/testing/assertions'
10
+
11
+ # load up the plugin in question
12
+ require 'active_record'
13
+ require 'active_record/fixtures'
14
+ require 'authenticates_access'
15
+
16
+ ActiveRecord::Base.class_eval do
17
+ extend AuthenticatesAccess::ClassMethods
18
+ end
19
+
20
+ root_dir = File.dirname(__FILE__)
21
+
22
+ # do cocaine to make ActiveRecord happy
23
+ ActiveRecord::Base.configurations = YAML::load(File.open(File.join(root_dir, 'database.yml')))
24
+
25
+ # bring up some database stuff
26
+ ActiveRecord::Base.establish_connection({
27
+ :adapter => 'sqlite3',
28
+ :dbfile => ':memory:'
29
+ })
30
+
31
+ def build_schema
32
+ # define a crude schema
33
+ ActiveRecord::Schema.define do
34
+ create_table "users", :force => true do |t|
35
+ t.column "name", :string
36
+ t.column "is_admin", :boolean
37
+ t.column "bio", :text
38
+ end
39
+
40
+ create_table "owned_items", :force => true do |t|
41
+ t.column "user_id", :integer
42
+ t.column "description", :text
43
+ end
44
+
45
+ create_table "admin_items", :force => true do |t|
46
+ t.column "description", :text
47
+ end
48
+
49
+ create_table "created_items", :force => true do |t|
50
+ t.column "user_id", :integer
51
+ t.column "description", :text
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ class ActiveSupport::TestCase
58
+ # 2.3.2 seems to need this?
59
+ begin
60
+ include ActiveRecord::TestFixtures
61
+ rescue NameError
62
+ puts "You appear to be using a pre-2.3 version of Rails. No need to include ActiveRecord::TestFixtures..."
63
+ end
64
+
65
+ self.fixture_path = File.join(File.dirname(__FILE__), "fixtures")
66
+
67
+ build_schema
68
+
69
+ self.use_transactional_fixtures = true
70
+ self.use_instantiated_fixtures = false
71
+
72
+ # load up fixtures
73
+ fixtures :all
74
+ end
75
+
data/test/user.rb ADDED
@@ -0,0 +1,14 @@
1
+ class User < ActiveRecord::Base
2
+ # users own themselves, so to speak
3
+ has_owner :self
4
+
5
+ # only admins can write to the is_admin and can_create fields
6
+ authenticates_writes_to :is_admin, :with_accessor_method => :is_admin
7
+ authenticates_writes_to :can_create, :with_accessor_method => :is_admin
8
+ # users can save their own profiles
9
+ authenticates_saves :with => :allow_owner
10
+ # admins can save anyone's profile
11
+ authenticates_saves :with_accessor_method => :is_admin
12
+ # admins can create users
13
+ authenticates_creation :with_accessor_method => :is_admin
14
+ end
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mikldt-authenticates_access
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael DiTore
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-24 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Model-based read and write user authorization in Rails
17
+ email: mikldt@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - .gitignore
26
+ - MIT-LICENSE
27
+ - README
28
+ - Rakefile
29
+ - VERSION
30
+ - init.rb
31
+ - install.rb
32
+ - lib/authenticates_access.rb
33
+ - mikldt-authenticates_access.gemspec
34
+ - tasks/authenticates_access_tasks.rake
35
+ - test/admin_item.rb
36
+ - test/authenticates_access_test.rb
37
+ - test/database.yml
38
+ - test/fixtures/admin_items.yml
39
+ - test/fixtures/owned_items.yml
40
+ - test/fixtures/users.yml
41
+ - test/owned_item.rb
42
+ - test/test_helper.rb
43
+ - test/user.rb
44
+ - uninstall.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/mikldt/authenticates_access
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Model-based Authorization on Rails!
73
+ test_files:
74
+ - test/authenticates_access_test.rb
75
+ - test/user.rb
76
+ - test/test_helper.rb
77
+ - test/owned_item.rb
78
+ - test/admin_item.rb