indulgence 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Rob Nichols
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.
@@ -0,0 +1,315 @@
1
+ = Indulgence
2
+
3
+ Yet another permissions gem.
4
+
5
+ In creating Indulgence I wanted a role based permissions tool that did two main
6
+ things:
7
+
8
+ * Determine what permission a user had to do something to an object
9
+ * Filtered a search of objects based on the those permissions
10
+
11
+ It was apparent to me that if 'something' was one of the CRUD actions, it would
12
+ cover all the use cases I could think of. So permissions were sub-divided into
13
+ the 'abilities': create, read, update, and delete.
14
+
15
+ The other requirement was that the permission for an object could be defined
16
+ succinctly within a single file.
17
+
18
+ == Defining indulgent permissions
19
+
20
+ Indulgence is added to a class via acts_as_indulgent:
21
+
22
+ class Thing < ActiveRecord::Base
23
+ acts_as_indulgent
24
+ end
25
+
26
+ Used in this way, permissions need to be defined in an Indulgence::Permission
27
+ object called ThingPermission, with an instance method :default
28
+
29
+ class ThingPermission < Indulgence::Permission
30
+
31
+ def default
32
+ {
33
+ create: none,
34
+ read: all,
35
+ update: none,
36
+ delete: none
37
+ }
38
+ end
39
+
40
+ end
41
+
42
+ This needs to be available to the Thing class. For example, in a rails app, by
43
+ placing it in app/permissions/thing_permission.rb
44
+
45
+ == Users and Roles
46
+
47
+ Indulgence assumes that permissions will be tested against an entity object
48
+ (e.g. User), that has a role object associated with it, and that each role can
49
+ be uniquely identified by a name method.
50
+
51
+ The default behaviour assumes that the entity object has a :role method that
52
+ returns the role object, and that the role object has a :name method. So
53
+ typically, these objects could look like this:
54
+
55
+ class User < ActiveRecord::Base
56
+ belongs_to :role
57
+ end
58
+
59
+ class Role < ActiveRecord::Base
60
+ has_many :users
61
+ validates :name, :uniqueness => true
62
+ end
63
+
64
+ role = Role.create(:name => 'pleb')
65
+ user = User.create(
66
+ :first_name => 'Joe',
67
+ :last_name => 'Blogs',
68
+ :role_id => role.id
69
+ )
70
+
71
+ == indulge?
72
+
73
+ Simple true/false permission can be determined using the :indulge? method:
74
+
75
+ thing = Thing.first
76
+
77
+ thing.indulge?(user, :create) == false
78
+ thing.indulge?(user, :read) == true
79
+ thing.indulge?(user, :update) == false
80
+ thing.indulge?(user, :delete) == false
81
+
82
+ == indulgence
83
+
84
+ The :indulgence method is used as a where filter:
85
+
86
+ Thing.indulgence(user, :create) --> raises ActiveRecord::RecordNotFound
87
+ Thing.indulgence(user, :read) == Thing.all
88
+ Thing.indulgence(user, :update) --> raises ActiveRecord::RecordNotFound
89
+ Thing.indulgence(user, :delete) --> raises ActiveRecord::RecordNotFound
90
+
91
+ So to find all the blue things that the user has permission to read:
92
+
93
+ Thing.indulgence(user, :read).where(:colour => 'blue')
94
+
95
+ == Customisation
96
+
97
+ === Adding other roles
98
+
99
+ Up until now, all users get the same permissions irrespective of role. Let's
100
+ give Emperors the right to see and do anything by first creating an emperor
101
+
102
+ emperor = Role.create(:name => 'emperor')
103
+ caesar = User.create(
104
+ :first_name => 'Julius',
105
+ :last_name => 'Ceasar',
106
+ :role_id => emporor.id
107
+ )
108
+
109
+ And then defining what they can do by adding these two methods to ThingPermission:
110
+
111
+ def abilities
112
+ {
113
+ emperor: default.merge(emperor)
114
+ }
115
+ end
116
+
117
+ def emperor
118
+ {
119
+ create: all,
120
+ update: all,
121
+ delete: all
122
+ }
123
+ end
124
+
125
+ This uses a merger of the default abilities so that only the variant abilities
126
+ need to be defined in the emperor method. That is, _read_ is inherited from
127
+ _default_ rather than being defined in _emperor_, as it is already set to *all*.
128
+
129
+ abilities is a hash of hashes. The lowest level, associates action names with
130
+ ability objects. The top level associates role names to the lower level ability
131
+ object hashes. The construction is perhaps clearer in the abilities above was
132
+ written like this:
133
+
134
+ def abilities
135
+ {
136
+ emperor: {
137
+ create: all,
138
+ read: default[:read],
139
+ update: all,
140
+ delete: all
141
+ }
142
+ }
143
+ end
144
+
145
+ With this done:
146
+
147
+ thing.indulge?(caesar, :create) == true
148
+ thing.indulge?(caesar, :read) == true
149
+ thing.indulge?(caesar, :update) == true
150
+ thing.indulge?(caesar, :delete) == true
151
+
152
+ Thing.indulgence(caesar, :create) == Thing.all
153
+ Thing.indulgence(caesar, :read) == Thing.all
154
+ Thing.indulgence(caesar, :update) == Thing.all
155
+ Thing.indulgence(caesar, :delete) == Thing.all
156
+
157
+ === Adding abilities
158
+
159
+ Indulgence has two built in abilities. These are *all* and *none*. These two
160
+ have provided all the functionality described above, but in most real cases
161
+ some more fine tuned ability setting will be needed.
162
+
163
+ Let's create an author role, and give authors the ability to create and update
164
+ their own things.
165
+
166
+ author = Role.create(:name => :author)
167
+
168
+ Next we need to give author's ownership of things. So we add an :author_id
169
+ attribute to Thing, and a matching :author method:
170
+
171
+ class Thing < ActiveRecord::Base
172
+ acts_as_indulgent
173
+ belongs_to :author, :class_name => 'User'
174
+ end
175
+
176
+ Then we need to create an Ability that uses this relationship to determine
177
+ permissions. This can be done by adding this method to ThingPermission:
178
+
179
+ def things_they_wrote
180
+ define_ability(
181
+ :name => :things_they_wrote,
182
+ :truth => lambda {|thing| thing.author_id == entity.id},
183
+ :where_clause => {:author_id => entity.id}
184
+ )
185
+ end
186
+
187
+ This will create an Ability object with the following methods:
188
+
189
+ [name] Allows abilities of the same kind to be matched
190
+ [truth] Used by :indulge?
191
+ [where_clause] Used by :indulgence
192
+
193
+ Note that Indulgence::Permission#entity returns the entity object passed to the
194
+ instance on creation. In this example, that will be the User.
195
+
196
+ ==== The where clause
197
+
198
+ In this example:
199
+
200
+ Thing.indulgence(user, :read) == Thing.where(:author_id => user.id)
201
+
202
+ if the :read ability is matched to *things_they_wrote*
203
+
204
+ ==== truth
205
+
206
+ In *all* :truth is simply _true_, and in *none* :truth is _false_.
207
+
208
+ For more complex abilities :truth should be a lambda that is passed the object
209
+ being tested, and returns _true_ if permission should be given. Otherwise _false_
210
+ should be returned.
211
+
212
+ Once *things_they_wrote* has been defined, we can use it to define a new set
213
+ of abilities:
214
+
215
+ def abilities
216
+ {
217
+ emperor: default.merge(emperor),
218
+ author: default.merge(author)
219
+ }
220
+ end
221
+
222
+ def author
223
+ {
224
+ create: things_they_wrote,
225
+ update: things_they_wrote
226
+ }
227
+ end
228
+
229
+ With that done:
230
+
231
+ cicero = User.create(
232
+ :first_name => 'Marcus',
233
+ :last_name => 'Cicero',
234
+ :role_id => author.id
235
+ )
236
+
237
+ thing.author = cicero
238
+ thing.save
239
+
240
+ thing.indulge?(cicero, :create) == true
241
+ thing.indulge?(cicero, :read) == true
242
+ thing.indulge?(cicero, :update) == true
243
+ thing.indulge?(cicero, :delete) == false
244
+
245
+ Thing.indulgence(cicero, :create) == [thing]
246
+ Thing.indulgence(cicero, :read) == Thing.all
247
+ Thing.indulgence(cicero, :update) == [thing]
248
+ Thing.indulgence(cicero, :delete) --> raises ActiveRecord::RecordNotFound
249
+
250
+ === Alternative Permission Class
251
+
252
+ As stated above, the default behaviour is for a Thing class to expect its
253
+ permissions to be defined in ThingPermission. However, you can define an
254
+ alternative class name:
255
+
256
+ acts_as_indulgent :using => PermissionsForThing
257
+
258
+ === Alternative Entity behaviour
259
+
260
+ Consider this example:
261
+
262
+ class User < ActiveRecord::Base
263
+ belongs_to :position
264
+ end
265
+
266
+ class Position < ActiveRecord::Base
267
+ has_many :users
268
+ validates :title, :uniqueness => true
269
+ end
270
+
271
+ There are two ways of dealing with this.
272
+
273
+ If only ThingPermission is affected, the attributes that stores the _role_method_
274
+ and _role_name_method_ could be overwritten:
275
+
276
+ class ThingPermission < Indulgence::Permission
277
+ def self.role_method
278
+ :position
279
+ end
280
+
281
+ def self.role_name_method
282
+ :title
283
+ end
284
+
285
+ .....
286
+ end
287
+
288
+ Alternatively if all permissions were to be altered the super class Permission
289
+ could be updated (for example in a rails initializer):
290
+
291
+ Indulgence::Permission.role_method = :position
292
+ Indulgence::Permission.role_name_method = :title
293
+
294
+ === Alternative method names
295
+
296
+ The method names _indulgence_ and _indulge?_ may not suit your application. If
297
+ you wish to use alternative names, they can be aliased like this:
298
+
299
+ acts_as_indulgent(
300
+ :truth_method => :permit?,
301
+ :where_method => :permitted
302
+ )
303
+
304
+ With this used to define indulgence in Thing, we can do this:
305
+
306
+ thing.permit?(cicero, :update) == true
307
+ thing.permit?(cicero, :delete) == false
308
+
309
+ Thing.permitted(cicero, :create) == [thing]
310
+ Thing.permitted(cicero, :read) == Thing.all
311
+
312
+ == Examples
313
+
314
+ For some examples, have a look at the tests. In particular, look at the object
315
+ definitions in test/lib.
@@ -0,0 +1,13 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
11
+ require 'standalone_migrations'
12
+ StandaloneMigrations::Tasks.load_tasks
13
+
@@ -0,0 +1,42 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Indulgent
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def acts_as_indulgent(args = {})
11
+ include Indulgence::Indulgent::InstanceMethods
12
+ extend Indulgence::Indulgent::ClassMethods
13
+
14
+ alias_method args[:truth_method], :indulge? if args[:truth_method]
15
+ singleton_class.send(:alias_method, args[:where_method], :indulgence) if args[:where_method]
16
+ self.indulgent_permission_class = args[:using]
17
+ end
18
+
19
+ def indulgent_permission_class
20
+ @indulgent_permission_class
21
+ end
22
+
23
+ private
24
+ def default_indulgence_permission_class
25
+ class_name = to_s
26
+ name = class_name + "Permission"
27
+ require name.underscore
28
+ if const_defined? name
29
+ class_eval name
30
+ end
31
+ end
32
+
33
+ def indulgent_permission_class=(klass = nil)
34
+ @indulgent_permission_class = klass || default_indulgence_permission_class
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ ActiveRecord::Base.send(:include, ActiveRecord::Acts::Indulgent)
@@ -0,0 +1,7 @@
1
+ require_relative 'indulgence/permission'
2
+ require_relative 'indulgence/indulgent'
3
+ require_relative 'active_record/acts/indulgent'
4
+
5
+ module Indulgence
6
+
7
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Indulgence
3
+ class Ability
4
+ attr_reader :name, :truth, :where_clause
5
+
6
+ def initialize(args = {})
7
+ @name = args[:name]
8
+ @truth = args[:truth]
9
+ @where_clause = args[:where_clause]
10
+ end
11
+
12
+ def ==(another_ability)
13
+ [:name, :truth, :where_clause].each do |method|
14
+ return false if send(method) != another_ability.send(method)
15
+ end
16
+ return true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module Indulgence
2
+ module Indulgent
3
+
4
+ module ClassMethods
5
+ def indulgence(entity, ability)
6
+ permission = indulgent_permission_class.new(entity, ability)
7
+ raise_not_found if permission.ability == Permission.none or permission.ability.blank?
8
+ where(permission.where)
9
+ end
10
+
11
+ private
12
+ def raise_not_found
13
+ raise ActiveRecord::RecordNotFound.new('Unable to find the item(s) you were looking for')
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ def indulge?(entity, ability)
20
+ permission = self.class.indulgent_permission_class.new(entity, ability)
21
+ return permission.indulge? self
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,104 @@
1
+ require_relative 'ability'
2
+
3
+ module Indulgence
4
+ class Permission
5
+ attr_reader :entity, :ability
6
+
7
+ def initialize(entity, ability)
8
+ self
9
+ @entity = entity
10
+ @ability = abilities_for_role[ability]
11
+ end
12
+
13
+ def abilities
14
+ {}
15
+ end
16
+
17
+ def default
18
+ raise 'There must always be a default'
19
+ end
20
+
21
+ def where
22
+ ability.where_clause
23
+ end
24
+
25
+ def indulge?(thing)
26
+ ability.truth.respond_to?(:call) ? ability.truth.call(thing) : ability.truth
27
+ end
28
+
29
+ @@role_method = :role
30
+
31
+ def self.role_method=(name)
32
+ @@role_method = name.to_sym
33
+ end
34
+
35
+ def self.role_method
36
+ @@role_method
37
+ end
38
+
39
+ @@role_name_method = :name
40
+
41
+ def self.role_name_method=(name)
42
+ @@role_name_method = name.to_sym
43
+ end
44
+
45
+ def self.role_name_method
46
+ @@role_name_method
47
+ end
48
+
49
+ def self.none
50
+ @none ||= define_ability(
51
+ :name => :none,
52
+ :truth => false
53
+ )
54
+ end
55
+
56
+ def self.all
57
+ @all ||= define_ability(
58
+ :name => :all,
59
+ :truth => true
60
+ )
61
+ end
62
+
63
+ def self.define_ability(args)
64
+ Ability.new args
65
+ end
66
+
67
+ def define_ability(args)
68
+ self.class.define_ability(args)
69
+ end
70
+
71
+ # Ensure passing an unknown key behaves as one would expect for a hash
72
+ def [](key)
73
+ return {}[key] unless keys.include? key
74
+ super
75
+ end
76
+
77
+ def role_name
78
+ @role_name ||= entity_role_name
79
+ end
80
+
81
+ def abilities_for_role
82
+ abilities[role_name] || default
83
+ end
84
+
85
+ private
86
+ def entity_role_name
87
+ role = entity.send(self.class.role_method)
88
+ name_method = self.class.role_name_method
89
+ if role and abilities.keys.include?(role.send(name_method).to_sym)
90
+ role.send(name_method).to_sym
91
+ else
92
+ :default
93
+ end
94
+ end
95
+
96
+ def all
97
+ self.class.all
98
+ end
99
+
100
+ def none
101
+ self.class.none
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,10 @@
1
+ module Indulgence
2
+ VERSION = "0.0.1"
3
+ end
4
+
5
+ # History
6
+ # =======
7
+ #
8
+ # 0.0.1 Initial build
9
+ # First alpa version
10
+ #
@@ -0,0 +1,17 @@
1
+ test: &test
2
+ adapter: sqlite3
3
+ database: test/db/test.sqlite3.db
4
+ pool: 5
5
+ timeout: 5000
6
+
7
+ develop:
8
+ adapter: sqlite3
9
+ database: test/db/develop.sqlite3.db
10
+ pool: 5
11
+ timeout: 5000
12
+
13
+ production:
14
+ adapter: sqlite3
15
+ database: test/db/production.sqlite3.db
16
+ pool: 5
17
+ timeout: 5000
@@ -0,0 +1,11 @@
1
+ require 'active_record'
2
+ class CreateUsers < ActiveRecord::Migration
3
+ def change
4
+ create_table :users do |t|
5
+ t.string :name
6
+ t.integer :role_id
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+ class CreateRoles < ActiveRecord::Migration
3
+ create_table :roles do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class CreateThings < ActiveRecord::Migration
2
+ create_table :things do |t|
3
+ t.string :name
4
+ t.integer :owner_id
5
+ t.timestamps
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20130408132217) do
15
+
16
+ create_table "roles", :force => true do |t|
17
+ t.string "name"
18
+ t.datetime "created_at", :null => false
19
+ t.datetime "updated_at", :null => false
20
+ end
21
+
22
+ create_table "things", :force => true do |t|
23
+ t.string "name"
24
+ t.integer "owner_id"
25
+ t.datetime "created_at", :null => false
26
+ t.datetime "updated_at", :null => false
27
+ end
28
+
29
+ create_table "users", :force => true do |t|
30
+ t.string "name"
31
+ t.integer "role_id"
32
+ t.datetime "created_at", :null => false
33
+ t.datetime "updated_at", :null => false
34
+ end
35
+
36
+ end
Binary file
@@ -0,0 +1,7 @@
1
+ class Role < ActiveRecord::Base
2
+ has_many :users
3
+
4
+ def title
5
+ self.name
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+
2
+ class Thing < ActiveRecord::Base
3
+
4
+ belongs_to :owner, :class_name => 'User'
5
+
6
+ acts_as_indulgent(
7
+ :truth_method => :permit?,
8
+ :where_method => :permitted
9
+ )
10
+
11
+ end
12
+
13
+ class OtherThing < Thing
14
+
15
+ acts_as_indulgent :using => ThingPermission
16
+
17
+ end
18
+
19
+
@@ -0,0 +1,46 @@
1
+ require 'indulgence'
2
+
3
+ class ThingPermission < Indulgence::Permission
4
+
5
+ def abilities
6
+ {
7
+ god: default.merge(god),
8
+ demigod: default.merge(demigod)
9
+ }
10
+ end
11
+
12
+
13
+ def default
14
+ {
15
+ create: none,
16
+ read: all,
17
+ update: none,
18
+ delete: none
19
+ }
20
+ end
21
+
22
+ def god
23
+ {
24
+ create: all,
25
+ update: all,
26
+ delete: all
27
+ }
28
+ end
29
+
30
+ def demigod
31
+ {
32
+ create: things_they_own,
33
+ update: things_they_own,
34
+ delete: things_they_own
35
+ }
36
+ end
37
+
38
+ def things_they_own
39
+ @things_they_own ||= define_ability(
40
+ :name => :things_they_own,
41
+ :truth => lambda {|thing| thing.owner_id == entity.id},
42
+ :where_clause => {:owner_id => entity.id}
43
+ )
44
+ end
45
+
46
+ end
@@ -0,0 +1,9 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ belongs_to :role
4
+
5
+ has_many :things, :foreign_key => 'owner_id'
6
+
7
+ alias_method :position, :role
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ $:.unshift File.join(File.dirname(__FILE__),'units')
3
+ $:.unshift File.join(File.dirname(__FILE__),'lib')
4
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib','indulgence')
5
+
6
+ require 'test/unit'
7
+
8
+ require 'active_record'
9
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => "test/db/test.sqlite3.db"
@@ -0,0 +1,36 @@
1
+ require_relative '../../test_helper'
2
+ require 'ability'
3
+
4
+ module Indulgence
5
+ class AbilityTest < Test::Unit::TestCase
6
+
7
+ def test_none
8
+ none = Ability.new(
9
+ :name => :none,
10
+ :truth => false
11
+ )
12
+ assert_equal(:none, none.name)
13
+ assert_equal(false, none.truth)
14
+ assert_equal(nil, none.where_clause)
15
+ end
16
+
17
+ def test_all
18
+ all = Ability.new(
19
+ :name => :all,
20
+ :truth => true
21
+ )
22
+ assert_equal(:all, all.name)
23
+ assert_equal(true, all.truth)
24
+ assert_equal(nil, all.where_clause)
25
+ end
26
+
27
+ def test_equality
28
+ attributes = {:name => :same, :true => true}
29
+ ability = Ability.new(attributes)
30
+ other_ability = Ability.new(attributes)
31
+ assert(ability == other_ability, "#{ability} == #{other_ability} should return true")
32
+ assert_equal(ability, other_ability)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../../test_helper'
2
+ require 'user'
3
+
4
+ module Indulgence
5
+ class PermissionTest < Test::Unit::TestCase
6
+
7
+ def test_creation_fails_as_methods_undefined_in_parent_class
8
+ assert_raise RuntimeError do
9
+ Permission.new(User.create(:name => 'Whisp'), :read)
10
+ end
11
+ end
12
+
13
+
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'role'
4
+
5
+ class RoleTest < Test::Unit::TestCase
6
+ def teardown
7
+ User.delete_all
8
+ Role.delete_all
9
+ end
10
+
11
+ def test_users
12
+ role = Role.create(:name => 'god')
13
+ user = User.create(:name => 'Harry', :role_id => role.id)
14
+ assert_equal([user], role.users)
15
+ end
16
+ end
@@ -0,0 +1,78 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'role'
4
+ require 'thing_permission'
5
+
6
+ class ThingPermissionTest < Test::Unit::TestCase
7
+
8
+ Permission = Indulgence::Permission
9
+
10
+ def setup
11
+ god = Role.create(:name => 'god')
12
+ mortal = Role.create(:name => 'mortal')
13
+ @user = User.create(:name => 'Trevor', :role_id => mortal.id)
14
+ @super_user = User.create(:name => 'Jane', :role_id => god.id)
15
+ @original_role_name = Permission.role_method
16
+ @original_role_name_method = Permission.role_name_method
17
+ end
18
+
19
+ def test_super_user_permissions
20
+ assert_equal Permission.all, ThingPermission.new(@super_user, :create).ability
21
+ assert_equal Permission.all, ThingPermission.new(@super_user, :read).ability
22
+ assert_equal Permission.all, ThingPermission.new(@super_user, :update).ability
23
+ assert_equal Permission.all, ThingPermission.new(@super_user, :delete).ability
24
+ end
25
+
26
+ def test_default_permissions
27
+ assert_equal Permission.none, ThingPermission.new(@user, :create).ability
28
+ assert_equal Permission.all, ThingPermission.new(@user, :read).ability
29
+ assert_equal Permission.none, ThingPermission.new(@user, :update).ability
30
+ assert_equal Permission.none, ThingPermission.new(@user, :delete).ability
31
+ end
32
+
33
+ def test_role_name
34
+ assert_equal @super_user.role.name.to_sym, ThingPermission.new(@super_user, :read).role_name
35
+ end
36
+
37
+ def test_role_name_when_not_defined_in_permissions
38
+ assert_equal :default, ThingPermission.new(@user, :read).role_name
39
+ end
40
+
41
+ def test_setting_unknown_role_method_causes_error
42
+ Permission.role_method = :something_else
43
+ assert_raise NoMethodError do
44
+ ThingPermission.new(@super_user, :read).role_name
45
+ end
46
+ end
47
+
48
+ def test_setting_role_method
49
+ Permission.role_method = :position
50
+ test_super_user_permissions
51
+ end
52
+
53
+ def test_setting_unknown_role_name_method_causes_error
54
+ Permission.role_name_method = :something_else
55
+ assert_raise NoMethodError do
56
+ ThingPermission.new(@super_user, :read).role_name
57
+ end
58
+ end
59
+
60
+ def test_setting_role_name_method
61
+ Permission.role_name_method = :title
62
+ test_super_user_permissions
63
+ end
64
+
65
+ def teardown
66
+ User.delete_all
67
+ Role.delete_all
68
+ Thing.delete_all
69
+ Permission.role_method = @original_role_name
70
+ Permission.role_name_method = @original_role_name_method
71
+ end
72
+
73
+ def make_things
74
+ @users_thing = Thing.create(:one, :owner_id => @user.id)
75
+ @super_users_thing = Thing.create(:two, :owner_id => @super_user.id)
76
+ end
77
+
78
+ end
@@ -0,0 +1,98 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'thing'
4
+
5
+ class ThingTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @god = Role.create(:name => 'god')
9
+ @demigod = Role.create(:name => 'demigod')
10
+ @owner = User.create(:name => 'Mary')
11
+ @thing = Thing.create(:name => 'Stuff', :owner_id => @owner.id)
12
+ end
13
+
14
+
15
+ def test_user
16
+ assert_equal(@owner, @thing.owner)
17
+ assert_equal([@thing], @owner.things)
18
+ end
19
+
20
+ def test_indulge
21
+ make_second_thing
22
+ assert_equal(true, @thing.indulge?(@owner, :read))
23
+ assert_equal(false, @thing.indulge?(@owner, :delete))
24
+ assert_equal(false, @other_thing.indulge?(@owner, :delete))
25
+ end
26
+
27
+ def test_indulge_by_god
28
+ make_second_thing
29
+ @owner.update_attribute(:role_id, @god.id)
30
+ assert_equal(true, @thing.indulge?(@owner, :read))
31
+ assert_equal(true, @thing.indulge?(@owner, :delete))
32
+ assert_equal(true, @other_thing.indulge?(@owner, :delete))
33
+ end
34
+
35
+ def test_indulge_by_demigod
36
+ make_second_thing
37
+ @owner.update_attribute(:role_id, @demigod.id)
38
+ assert_equal(true, @thing.indulge?(@owner, :read))
39
+ assert_equal(true, @thing.indulge?(@owner, :delete))
40
+ assert_equal(false, @other_thing.indulge?(@owner, :delete))
41
+ end
42
+
43
+ def test_indulge_other_thing
44
+ other_thing = OtherThing.create(:name => 'Other Stuff', :owner_id => @owner.id)
45
+ assert_equal(true, other_thing.indulge?(@owner, :read))
46
+ assert_equal(false, other_thing.indulge?(@owner, :delete))
47
+ end
48
+
49
+ def test_indulgence
50
+ make_second_thing
51
+ @owner.update_attribute(:role_id, @demigod.id)
52
+ assert_equal(Thing.order('id'), Thing.indulgence(@owner, :read).order('id'))
53
+ assert_equal(Thing.order('id'), Thing.indulgence(@user, :read).order('id'))
54
+ assert_equal([@thing], Thing.indulgence(@owner, :delete))
55
+ assert_raise ActiveRecord::RecordNotFound do
56
+ Thing.indulgence(@user, :delete)
57
+ end
58
+ end
59
+
60
+ def test_find
61
+ make_second_thing
62
+ @owner.update_attribute(:role_id, @demigod.id)
63
+ assert_equal(@thing, Thing.indulgence(@owner, :delete).find(@thing.id))
64
+ assert_raise ActiveRecord::RecordNotFound do
65
+ assert_equal(@thing, Thing.indulgence(@user, :delete).find(@thing.id))
66
+ end
67
+ end
68
+
69
+ def test_truth_method
70
+ make_second_thing
71
+ assert_equal(true, @thing.permit?(@owner, :read))
72
+ assert_equal(false, @thing.permit?(@owner, :delete))
73
+ assert_equal(false, @other_thing.permit?(@owner, :delete))
74
+ end
75
+
76
+ def test_where_method
77
+ make_second_thing
78
+ @owner.update_attribute(:role_id, @demigod.id)
79
+ assert_equal(Thing.order('id'), Thing.permitted(@owner, :read).order('id'))
80
+ assert_equal(Thing.order('id'), Thing.permitted(@user, :read).order('id'))
81
+ assert_equal([@thing], Thing.permitted(@owner, :delete))
82
+ assert_raise ActiveRecord::RecordNotFound do
83
+ Thing.permitted(@user, :delete)
84
+ end
85
+ end
86
+
87
+ def make_second_thing
88
+ @user = User.create(:name => 'Clive')
89
+ @other_thing = Thing.create(:name => 'Debris', :owner_id => @user.id)
90
+ end
91
+
92
+ def teardown
93
+ Role.delete_all
94
+ User.delete_all
95
+ Thing.delete_all
96
+ end
97
+
98
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'role'
4
+
5
+ class UserTest < Test::Unit::TestCase
6
+ def teardown
7
+ User.delete_all
8
+ Role.delete_all
9
+ end
10
+
11
+ def test_create
12
+ user = User.create(:name => 'Bob')
13
+ assert_equal(user, User.find_by_name('Bob'))
14
+ end
15
+
16
+ def test_role
17
+ role = Role.create(:name => 'god')
18
+ user = User.create(:name => 'Harry', :role_id => role.id)
19
+ assert_equal(role, user.role)
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: indulgence
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rob Nichols
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sqlite3
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Packages permission functionality into a set of permission objects.
47
+ email:
48
+ - rob@undervale.co.uk
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/active_record/acts/indulgent.rb
54
+ - lib/indulgence/version.rb
55
+ - lib/indulgence/indulgent.rb
56
+ - lib/indulgence/ability.rb
57
+ - lib/indulgence/permission.rb
58
+ - lib/indulgence.rb
59
+ - MIT-LICENSE
60
+ - Rakefile
61
+ - README.rdoc
62
+ - test/units/role_test.rb
63
+ - test/units/indulgence/permission_test.rb
64
+ - test/units/indulgence/ability_test.rb
65
+ - test/units/user_test.rb
66
+ - test/units/thing_permission_test.rb
67
+ - test/units/thing_test.rb
68
+ - test/lib/role.rb
69
+ - test/lib/user.rb
70
+ - test/lib/thing.rb
71
+ - test/lib/thing_permission.rb
72
+ - test/db/migrate/20130408103015_create_roles.rb
73
+ - test/db/migrate/20130408085511_create_users.rb
74
+ - test/db/migrate/20130408132217_create_things.rb
75
+ - test/db/config.yml
76
+ - test/db/test.sqlite3.db
77
+ - test/db/schema.rb
78
+ - test/test_helper.rb
79
+ homepage: https://github.com/reggieb/indulgence
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 1.8.24
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Yet another permissions gem
103
+ test_files:
104
+ - test/units/role_test.rb
105
+ - test/units/indulgence/permission_test.rb
106
+ - test/units/indulgence/ability_test.rb
107
+ - test/units/user_test.rb
108
+ - test/units/thing_permission_test.rb
109
+ - test/units/thing_test.rb
110
+ - test/lib/role.rb
111
+ - test/lib/user.rb
112
+ - test/lib/thing.rb
113
+ - test/lib/thing_permission.rb
114
+ - test/db/migrate/20130408103015_create_roles.rb
115
+ - test/db/migrate/20130408085511_create_users.rb
116
+ - test/db/migrate/20130408132217_create_things.rb
117
+ - test/db/config.yml
118
+ - test/db/test.sqlite3.db
119
+ - test/db/schema.rb
120
+ - test/test_helper.rb