mikldt-authenticates_access 0.0.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.
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