cancannible 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ module Cancannible
2
+
3
+ mattr_accessor :refinements
4
+ mattr_accessor :get_cached_abilities
5
+ mattr_accessor :store_cached_abilities
6
+
7
+ # Default way to configure the gem. Yields a block that gives access to all the config variables.
8
+ # Calling setup will reset all existing values.
9
+ def self.setup
10
+ reset!
11
+ yield self
12
+ self
13
+ end
14
+
15
+ def self.reset!
16
+ self.refinements = []
17
+ self.get_cached_abilities = nil
18
+ self.store_cached_abilities = nil
19
+ end
20
+ reset!
21
+
22
+ def self.refine_access(refinement={})
23
+ self.refinements << refinement
24
+ end
25
+
26
+ end
@@ -0,0 +1,3 @@
1
+ module Cancannible
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_record'
4
+
5
+ module Cancannible
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+
10
+ self.source_paths << File.join(File.dirname(__FILE__), 'templates')
11
+
12
+ desc "This generator creates a cancannible initializer file and permissions model migration"
13
+
14
+ def create_initializer_file
15
+ template 'cancannible_initializer.rb', 'config/initializers/cancannible.rb'
16
+ end
17
+
18
+ def create_permission_migration_file
19
+ migration_template 'migration.rb', 'db/migrate/create_cancannible_permissions.rb'
20
+ end
21
+
22
+ def create_permission_model_file
23
+ template 'permission.rb', 'app/models/permission.rb'
24
+ end
25
+
26
+ # while methods have moved around this has been the implementation
27
+ # since ActiveRecord 3.0
28
+ def self.next_migration_number(dirname)
29
+ next_migration_number = current_migration_number(dirname) + 1
30
+ if ActiveRecord::Base.timestamped_migrations
31
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
32
+ else
33
+ "%.3d" % next_migration_number
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,97 @@
1
+ Cancannible.setup do |config|
2
+
3
+ # ABILITY CACHING
4
+ # ===============
5
+ # Cancannible supports optional ability caching. This can provide a significant performance
6
+ # improvement, since abilities do not need to be recalculated on each web request.
7
+ # No specific caching technology is enforced: you can use whatever makes sense in your application
8
+ # environment. To enable caching, you just need to provide two methods here: `get_cached_abilities`
9
+ # and `store_cached_abilities`.
10
+
11
+ # Return an Ability object for +grantee+ or nil if not found
12
+ # config.get_cached_abilities = proc{|grantee|
13
+ # # This is a simple example of using Redis (assumes @redis correctly connected)
14
+ # key = "user:#{grantee.id}:abilities"
15
+ # Marshal.load(@redis.get(key))
16
+ # }
17
+
18
+ # Command: put the +ability+ object for +grantee+ in the cache storage
19
+ # config.store_cached_abilities = proc{|grantee,ability|
20
+ # # This is a simple example of using Redis (assumes @redis correctly connected)
21
+ # key = "user:#{grantee.id}:abilities"
22
+ # @redis.set(key, Marshal.dump(ability))
23
+ # }
24
+
25
+
26
+ # ACCESS REFINMENTS
27
+ # Cancannible allows general-purpose access refinements to be declared here. This will be enforced
28
+ # in addition to any rules defined in you Ability.rb file.
29
+
30
+ # These are primarily intended to enforce data partitioning (multi-tenancy) and general security rules such as:
31
+ # "only show data related to customers that I have permissions to see", or
32
+ # "make sure I can see records that I created (regardless of other restrictions)"
33
+
34
+ # The syntax here is not as flexible as that supported in the Ability.rb file, but have the benefit of
35
+ # potentially refining any/all permissions that are configured in the permissions tables.
36
+
37
+ # The following examples illustrate the options that are available. These can be used individually or in combination.
38
+
39
+
40
+ # Basic syntax example:
41
+ # config.refine_access customer_id: :accessible_customer_ids
42
+ #
43
+ # This means:
44
+ # - for any resource permission loaded/inherited from the database
45
+ # - where the resource has a :customer_id attribute
46
+ # - restrict access to only those with values from my (the grantee) :accessible_customer_ids method
47
+ #
48
+ # In other words, say we have a Contract model that has a :customer_id attribute, and permissions are granted thus:
49
+ # user.can(:read,Contract)
50
+ # Then if we define a user.accessible_customer_ids method, these values will be used to restrict user's access to Contract records
51
+ #
52
+ # Note: the rule is ignored if the resource does not sport the attribute mentioned, or if the grantee method is not defined.
53
+
54
+
55
+ # Multiple conditions syntax example:
56
+ # config.refine_access customer_id: :accessible_customer_ids, product_id: :accessible_product_ids
57
+ #
58
+ # This restricts access to only records matching all conditions
59
+
60
+
61
+ # Fixed value conditions syntax example:
62
+ # config.refine_access customer_id: 42, status: 'open'
63
+ #
64
+ # Fixed match-values may be provided instead of methods
65
+
66
+
67
+ # Limited ability scope syntax example:
68
+ # config.refine_access customer_id: :accessible_customer_ids, scope: :read
69
+ # config.refine_access customer_id: :accessible_customer_ids, scope: [:read,:update]
70
+ #
71
+ # The `scope` parameter causes the rule to only be applied for the specified ability rules
72
+
73
+
74
+ # Limited ability scope syntax example:
75
+ # config.refine_access customer_id: :accessible_customer_ids, except: :read
76
+ # config.refine_access customer_id: :accessible_customer_ids, except: [:read,:update]
77
+ #
78
+ # The `except` parameter causes the rule to be applied for all abilities except those specified.
79
+
80
+
81
+ # Allow nil syntax example:
82
+ # config.refine_access customer_id: :accessible_customer_ids, allow_nil: true
83
+ #
84
+ # The `allow_nil` parameter changes the rule so that nil/NULL values are allowed through. By default, this is false.
85
+
86
+
87
+ # Conditional syntax example:
88
+ # config.refine_access customer_id: :accessible_customer_ids, if: proc{|grantee,model_resource|
89
+ # grantee.name = 'Paul' && model_resource.is_a?(Special)
90
+ # }
91
+ #
92
+ # The `if` parameter allowws you to provide a procedure that will dynamically determine if the rule should be applied.
93
+ # It should return true or false. The `grantee` is the actual instance that permissions are being applied to,
94
+ # and `model_resource` is an example record (unsaved) of the kind of resource that the rule is being applied to.
95
+
96
+
97
+ end
@@ -0,0 +1,15 @@
1
+ class CreateCancanniblePermissions < ActiveRecord::Migration
2
+ def change
3
+ create_table :permissions, force: true do |table|
4
+ table.integer :permissible_id
5
+ table.string :permissible_type
6
+ table.integer :resource_id
7
+ table.string :resource_type
8
+ table.string :ability
9
+ table.boolean :asserted
10
+ table.datetime :created_at
11
+ table.datetime :updated_at
12
+ end
13
+ add_index :permissions, [:permissible_id, :permissible_type], name: "index_permissions_permissible"
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ # The Permission class stores permissions managed by CanCan and Cancannible
2
+ class Permission < ActiveRecord::Base
3
+ belongs_to :permissible, polymorphic: true
4
+ belongs_to :resource, polymorphic: true
5
+
6
+ validates_uniqueness_of :ability, scope: [:resource_id, :resource_type, :permissible_id, :permissible_type]
7
+
8
+ end
@@ -0,0 +1,16 @@
1
+ ENV["RAILS_ENV"] ||= 'test'
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'cancannible'
4
+ require 'active_record'
5
+ require 'sqlite3'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+ config.before do
13
+ Cancannible.reset!
14
+ run_migrations
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ class Ability
2
+ include CanCan::Ability
3
+ def initialize(user)
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ module MigrationsHelper
2
+
3
+ def run_migrations
4
+ ActiveRecord::Base.establish_connection({
5
+ adapter: 'sqlite3',
6
+ database: ':memory:'
7
+ })
8
+
9
+ ActiveRecord::Migration.suppress_messages do
10
+ ActiveRecord::Schema.define(:version => 0) do
11
+
12
+ create_table "members", :force => true do |t|
13
+ t.string "name"
14
+ t.string "email"
15
+ end
16
+
17
+ create_table "users", :force => true do |t|
18
+ t.string "username"
19
+ t.string "email"
20
+ t.integer "group_id"
21
+ end
22
+
23
+ create_table "permissions", :force => true do |t|
24
+ t.boolean "asserted"
25
+ t.integer "permissible_id"
26
+ t.string "permissible_type"
27
+ t.integer "resource_id"
28
+ t.string "resource_type"
29
+ t.string "ability"
30
+ t.datetime "created_at"
31
+ t.datetime "updated_at"
32
+ end
33
+
34
+ create_table "roles", :force => true do |t|
35
+ t.string "name"
36
+ end
37
+
38
+ create_table "roles_users", :force => true do |t|
39
+ t.string "name"
40
+ t.integer "role_id"
41
+ t.integer "user_id"
42
+ end
43
+
44
+ create_table "groups", :force => true do |t|
45
+ t.string "name"
46
+ end
47
+
48
+ create_table "widgets", :force => true do |t|
49
+ t.string "name"
50
+ t.integer "category_id"
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ RSpec.configure do |conf|
60
+ conf.include MigrationsHelper
61
+ end
@@ -0,0 +1,45 @@
1
+ # These model definitions are just used for the test scenarios
2
+
3
+ # The Permission class stores permissions maanged by CanCan and Cancannible
4
+ class Permission < ActiveRecord::Base
5
+ belongs_to :permissible, polymorphic: true
6
+ belongs_to :resource, polymorphic: true
7
+
8
+ validates_uniqueness_of :ability,
9
+ :scope => [:resource_id, :resource_type,
10
+ :permissible_id, :permissible_type]
11
+ end
12
+
13
+ class Member < ActiveRecord::Base
14
+ include Cancannible
15
+ end
16
+
17
+ class User < ActiveRecord::Base
18
+ has_many :roles_users, class_name: 'RolesUsers'
19
+ has_many :roles, :through => :roles_users
20
+ belongs_to :group
21
+
22
+ include Cancannible
23
+ inherit_permissions_from :roles, :group
24
+ end
25
+
26
+ class RolesUsers < ActiveRecord::Base
27
+ belongs_to :role
28
+ belongs_to :user
29
+ end
30
+
31
+ class Role < ActiveRecord::Base
32
+ has_many :roles_users, :class_name => 'RolesUsers'
33
+ has_many :users, :through => :roles_users
34
+
35
+ include Cancannible
36
+ end
37
+
38
+ class Group < ActiveRecord::Base
39
+ has_many :users
40
+
41
+ include Cancannible
42
+ end
43
+
44
+ class Widget < ActiveRecord::Base
45
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cancannible do
4
+ let(:grantee_class) { Member }
5
+
6
+ context "without permissions inheritance" do
7
+
8
+ describe "##inheritable_permissions" do
9
+ subject { grantee_class.inheritable_permissions }
10
+ it { should be_empty }
11
+ end
12
+
13
+ subject(:grantee) { grantee_class.create! }
14
+
15
+ describe "#abilities" do
16
+ subject(:abilities) { grantee.abilities }
17
+ it { should be_a(Ability) }
18
+ it "should initialise @abilities instance var" do
19
+ expect { abilities }.to change { grantee.instance_variable_get("@abilities") }.from(nil)
20
+ end
21
+ end
22
+
23
+ context "with a symbolic resource" do
24
+ let!(:resource) { :something }
25
+
26
+ describe "#can?" do
27
+ subject { grantee.can?(:read, resource) }
28
+ context "when permission is not set" do
29
+ it { should be_falsey }
30
+ end
31
+ context "when permission is set" do
32
+ before { grantee.can(:read, resource) }
33
+ it { should be_truthy }
34
+ end
35
+ context "when cannot is asserted" do
36
+ before { grantee.cannot(:read, resource) }
37
+ it { should be_falsey }
38
+ end
39
+ end
40
+
41
+ describe "#cannot?" do
42
+ subject { grantee.cannot?(:read, resource) }
43
+ context "when permission is not asserted" do
44
+ it { should be_truthy }
45
+ end
46
+ context "when permission is not asserted but can is" do
47
+ before { grantee.can(:read, resource) }
48
+ it { should be_falsey }
49
+ end
50
+ context "when permission is asserted" do
51
+ before { grantee.cannot(:read, resource) }
52
+ it { should be_truthy }
53
+ end
54
+ end
55
+ end
56
+
57
+ context "with a nil resource" do
58
+ let!(:resource) { nil }
59
+ describe "#can? -> nil" do
60
+ subject { grantee.can?(:read, nil) }
61
+ context "when permission is not set" do
62
+ it { should be_falsey }
63
+ end
64
+ context "when permission is set" do
65
+ before { grantee.can(:read, resource) }
66
+ it { should be_truthy }
67
+ end
68
+ end
69
+ describe "#can? -> ''" do
70
+ subject { grantee.can?(:read, '') }
71
+ context "when permission is not set" do
72
+ it { should be_falsey }
73
+ end
74
+ context "when permission is set" do
75
+ before { grantee.can(:read, resource) }
76
+ it { should be_falsey }
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ context "with a resource class" do
83
+ let!(:resource) { Widget }
84
+
85
+ describe "#can?" do
86
+ subject { grantee.can?(:read, resource) }
87
+ context "when permission is not set" do
88
+ it { should be_falsey }
89
+ end
90
+ context "when permission is set" do
91
+ before { grantee.can(:read, resource) }
92
+ it { should be_truthy }
93
+ end
94
+ end
95
+ end
96
+
97
+ context "with a resource instance" do
98
+ let!(:resource) { Widget.create! }
99
+ let!(:other_resource) { Widget.create! }
100
+
101
+ describe "#can?" do
102
+ subject { grantee.can?(:read, resource) }
103
+ context "when permission is not set" do
104
+ it { should be_falsey }
105
+ end
106
+ context "when permission is set" do
107
+ before { grantee.can(:read, resource) }
108
+ it { should be_truthy }
109
+ context "but for other instances" do
110
+ subject { grantee.can?(:read, other_resource) }
111
+ it { should be_falsey }
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ context "with a non-existent model" do
118
+ describe "instance" do
119
+ let!(:obsolete_permission) {grantee.permissions.create!(asserted: true, ability: 'manage', resource_type: 'Bogative', resource_id: 33) }
120
+ it "should not error on load" do
121
+ expect { grantee.abilities }.to_not raise_error
122
+ end
123
+ end
124
+ describe "class" do
125
+ let!(:obsolete_permission) {grantee.permissions.create!(asserted: true, ability: 'manage', resource_type: 'Bogative') }
126
+ it "should not error on load" do
127
+ grantee.abilities
128
+ expect { grantee.abilities }.to_not raise_error
129
+ end
130
+ end
131
+ end
132
+
133
+ context "with an invalid model" do
134
+ class SuperBogative < ActiveRecord::Base
135
+ end
136
+ context "instance" do
137
+ let!(:obsolete_permission) { grantee.permissions.create!(asserted: true, ability: 'manage', resource_type: 'SuperBogative', resource_id: 33) }
138
+ it "should not error on load" do
139
+ expect { grantee.abilities }.to_not raise_error
140
+ end
141
+ end
142
+ context "class" do
143
+ let!(:obsolete_permission) { grantee.permissions.create!(asserted: true, ability: 'manage', resource_type: 'SuperBogative') }
144
+ it "should not error on load" do
145
+ expect { grantee.abilities }.to_not raise_error
146
+ end
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ end