cancannible 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTJjNWE0NzMzZTcyNDAxMDRhOTMxZmE4ODRkYWZkMWU1ZDcwNjczMw==
5
+ data.tar.gz: !binary |-
6
+ NGY4MDliNGJlMGMwZjBkYTc5MjYyYzcyNWI0MWZiNWJmMmU1ZjU3Mw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTliYzQ2MGYzZWQyODhiMTk0ZTgzODhjNDQ1MDEwMzdmZDNjOWY0ZjRiY2Q0
10
+ NDNjZGUwYmE4OTQ3Zjc2MjZkMDc1N2JjOTYyOWE2ZGY4OWZhNGE5NTkyYjg4
11
+ ODFjZTYyODc0Yjg5ZGM4YWRmMjI1ZmU4ZmFmZGU5NmExMzhjZTE=
12
+ data.tar.gz: !binary |-
13
+ NmM5ODQ3NmUwNDIzYjJkYTZjZTZlYzhiNDEzMTAxYWQzMmE0ODU1OGJiNTFm
14
+ OWRjMzUyZWUzMmQxYTI1ZmQ2MGIzMjZjOWRkNTI0N2VlZjkyN2NjNjljM2Ez
15
+ ZmY5M2RkMzgyYTk1OWE5OGFkYzUwNTE1ZjRlNmQyNzVkYjkxNWE=
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .rvm*
24
+ .ruby*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ # These are specific configuration settings required for travis-ci
2
+ language: ruby
3
+ rvm:
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # currently testing on the 3.2 branch of rails
4
+ # gem "activemodel", '~> 3.2'
5
+ # gem "activerecord", '~> 3.2'
6
+
7
+ # Specify your gem's dependencies in cancannible.gemspec
8
+ gemspec
@@ -0,0 +1,18 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ # Note: The cmd option is now required due to the increasing number of ways
5
+ # rspec may be run, below are examples of the most common uses.
6
+ # * bundler: 'bundle exec rspec'
7
+ # * bundler binstubs: 'bin/rspec'
8
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
9
+ # installed the spring binstubs per the docs)
10
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
11
+ # * 'just' rspec: 'rspec'
12
+ guard :rspec, cmd: 'bundle exec rspec' do
13
+ watch(%r{^spec/.+_spec\.rb$})
14
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
15
+ watch('spec/spec_helper.rb') { "spec" }
16
+
17
+ end
18
+
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Paul Gallagher
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,85 @@
1
+ # Cancannible
2
+ [![Build Status](https://travis-ci.org/evendis/cancannible.svg?branch=master)](https://travis-ci.org/evendis/cancannible)
3
+
4
+ Cancannible is a gem that extends CanCan with a range of capabilities:
5
+ * database-persisted permissions
6
+ * export CanCan methods to the model layer (so that permissions can be applied in model methods, and easily set in a test case)
7
+ * permissions inheritance (so that, for example, a User can inherit permissions from Roles and/or Groups)
8
+ * caching of abilities (so that they don't need to be recalculated on each web request)
9
+ * general-purpose access refinements (so that, for example, CanCan will automatically enforce multi-tenant or other security restrictions)
10
+
11
+ ## Limitations
12
+ Cancannible's origin was in a web application that's been in production for over 3 years.
13
+ This gem is an initial refactoring as a separate component. It continues to be used in production, but
14
+ there are some limitations and constraints that will ideally be removed or changed over time:
15
+
16
+ * It only supports ActiveRecord for permissions storage (specifically, it has been tested with PostgreSQL and SQLite)
17
+ * It currently assumes permissions are stored in a Permission model with a specific structure
18
+ * It works with the [CanCan](https://github.com/ryanb/cancan) gem. It has not yet been tested with the new [CanCanCan](https://github.com/CanCanCommunity/cancancan) gem.
19
+ * It assumes and is only tested with Rails 3.2. Not yet with Rails 4.
20
+ * It assumes your CanCan rules are setup with the default `Ability` class
21
+
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ gem 'cancannible'
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install cancannible
36
+
37
+ ## Configuration
38
+
39
+ A generator is provided to create:
40
+ * a default initialization template
41
+ * a Permission model and migration
42
+
43
+ After installing the gem, run the generator:
44
+
45
+ $ rails generate cancannible:install
46
+
47
+ ## The Cancannible initialization file
48
+
49
+ See the initialization file template for specific instructions. Use the initialization file to configure:
50
+ * abilities caching
51
+ * general-purpose access refinements
52
+
53
+ ## Configuring cached abilities storage
54
+
55
+ Cancannible does not implement any specific storage mechanism - that is up to you to provide if you wish.
56
+
57
+ Cached abilities storage is enabled by setting the `get_cached_abilities` and `store_cached_abilities` hooks with
58
+ the appropriate implementation for your caching infrastructure.
59
+
60
+ For example, this is a simple scheme using Redis:
61
+
62
+ Cancannible.setup do |config|
63
+
64
+ # Return an Ability object for +grantee+ or nil if not found
65
+ config.get_cached_abilities = proc{|grantee|
66
+ key = "user:#{grantee.id}:abilities"
67
+ Marshal.load(@redis.get(key))
68
+ }
69
+
70
+ # Command: put the +ability+ object for +grantee+ in the cache storage
71
+ config.store_cached_abilities = proc{|grantee,ability|
72
+ key = "user:#{grantee.id}:abilities"
73
+ @redis.set(key, Marshal.dump(ability))
74
+ }
75
+
76
+ end
77
+
78
+
79
+ ## Contributing
80
+
81
+ 1. Fork it ( https://github.com/evendis/cancannible/fork )
82
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
83
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
84
+ 4. Push to the branch (`git push origin my-new-feature`)
85
+ 5. Create a new Pull Request
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc "Open an irb session preloaded with this library"
9
+ task :console do
10
+ sh "irb -rubygems -I lib -r cancannible.rb"
11
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cancannible/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cancannible"
8
+ spec.version = Cancannible::VERSION
9
+ spec.authors = ["Paul Gallagher"]
10
+ spec.email = ["paul@evendis.com"]
11
+ spec.summary = "Dynamic, configurable permissions for CanCan"
12
+ spec.description = "Extends CanCan with dynamic, inheritable permissions stored in a database, with caching and multi-tenant refinements"
13
+ spec.homepage = "https://github.com/evendis/cancannible"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "activesupport", "~> 3.2"
22
+ spec.add_runtime_dependency "activemodel", "~> 3.2"
23
+ spec.add_runtime_dependency "cancan", "~> 1.6"
24
+
25
+ spec.add_development_dependency "activerecord", "~> 3.2"
26
+ spec.add_development_dependency "sqlite3", "~> 1.3"
27
+ spec.add_development_dependency "bundler", "~> 1.6"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency "guard-rspec", "~> 4.0"
31
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'cancan'
4
+
5
+ require "cancannible/version"
6
+ require "cancannible/config"
7
+ require "cancannible/ability_preload_adapter"
8
+ require "cancannible/base"
@@ -0,0 +1,15 @@
1
+ module Cancannible::AbilityPreloadAdapter
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ # Tap Ability.new to first preload permissions via Cancannible
7
+ alias_method :cancan_initialize, :initialize
8
+ def initialize(user)
9
+ user.preload_abilities(self) if user.respond_to? :preload_abilities
10
+ cancan_initialize(user)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,213 @@
1
+ module Cancannible
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ class_attribute :inheritable_permissions
6
+ self.inheritable_permissions = [] # default
7
+
8
+ has_many :permissions, as: :permissible, dependent: :destroy do
9
+ # generally use instance.can method, not permissions<< directly
10
+ def <<(arg)
11
+ ability, resource = arg
12
+ resource = nil if resource.blank?
13
+ asserted = arg[2].nil? ? true : arg[2]
14
+
15
+ case resource
16
+ when Class, Symbol
17
+ resource_type = resource.to_s
18
+ resource_id = nil
19
+ when nil
20
+ resource_type = resource_id = nil
21
+ else
22
+ resource_type = resource.class.to_s
23
+ resource_id = resource.try(:id)
24
+ end
25
+
26
+ permission = find_by_asserted_and_ability_and_resource_id_and_resource_type(
27
+ asserted, ability, resource_id, resource_type)
28
+ unless permission
29
+ permission = find_or_initialize_by_asserted_and_ability_and_resource_id_and_resource_type(
30
+ !asserted, ability, resource_id, resource_type)
31
+ permission.asserted = asserted
32
+ permission.save!
33
+ end
34
+
35
+ # if Rails.version =~ /3\.0/ # the rails 3.0 way
36
+ # proxy_owner.instance_variable_set :@permissions, nil # invalidate the owner's permissions collection
37
+ # proxy_owner.instance_variable_set :@abilities, nil # invalidate the owner's ability collection
38
+ # else
39
+ proxy_association.owner.instance_variable_set :@abilities, nil # invalidate the owner's ability collection
40
+ # end
41
+ permission
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ module ClassMethods
48
+ def inherit_permissions_from(*relations)
49
+ self.inheritable_permissions = relations
50
+ end
51
+ end
52
+
53
+ # Returns the Ability set for the owner.
54
+ # Set +refresh+ to true to force a reload of permissions.
55
+ def abilities(refresh = false)
56
+ @abilities = if refresh
57
+ nil
58
+ elsif get_cached_abilities.respond_to?(:call)
59
+ get_cached_abilities.call(self)
60
+ end
61
+ return @abilities if @abilities
62
+
63
+ @abilities ||= if ability_class = ('Ability'.constantize rescue nil)
64
+ unless ability_class.included_modules.include?(Cancannible::AbilityPreloadAdapter)
65
+ ability_class.send :include, Cancannible::AbilityPreloadAdapter
66
+ end
67
+ ability_class.new(self)
68
+ end
69
+
70
+ store_cached_abilities.call(self,@abilities) if store_cached_abilities.respond_to?(:call)
71
+ @abilities
72
+ end
73
+
74
+ def preload_abilities(cancan_ability_object)
75
+ # load inherited permissions to CanCan Abilities
76
+ preload_abilities_from_permissions(cancan_ability_object, inherited_permissions)
77
+ # load user-based permissions from database to CanCan Abilities
78
+ preload_abilities_from_permissions(cancan_ability_object, self.permissions.reload)
79
+ cancan_ability_object
80
+ end
81
+
82
+ def inherited_permissions
83
+ inherited_perms = []
84
+ self.class.inheritable_permissions.each do |relation|
85
+ Array(self.send(relation)).each do |record|
86
+ inherited_perms.concat(record.permissions.reload)
87
+ end
88
+ end
89
+ inherited_perms
90
+ end
91
+
92
+ # test for a permission - persisted or dynamic (delegated to CanCan)
93
+ def can?(ability, resource)
94
+ abilities.can?(ability, resource)
95
+ end
96
+
97
+ # test for a prohibition - persisted or dynamic (delegated to CanCan)
98
+ def cannot?(ability, resource)
99
+ abilities.cannot?(ability, resource)
100
+ end
101
+
102
+ # define a persisted permission
103
+ def can(ability, resource)
104
+ permissions << [ability, resource]
105
+ end
106
+
107
+ # define a persisted prohibition
108
+ def cannot(ability, resource)
109
+ permissions << [ability, resource, false]
110
+ end
111
+
112
+ private
113
+
114
+ def preload_abilities_from_permissions(cancan_ability_object,perms)
115
+ perms.each do |permission|
116
+ ability = permission.ability.to_sym
117
+ action = permission.asserted ? :can : :cannot
118
+
119
+ if resource_type = permission.resource_type
120
+ begin
121
+ resource_type = resource_type==resource_type.downcase ? resource_type.to_sym : resource_type.constantize
122
+ model_resource = resource_type.respond_to?(:new) ? resource_type.new : resource_type
123
+ rescue
124
+ model_resource = nil
125
+ end
126
+ end
127
+
128
+ if !resource_type || resource_type.is_a?(Symbol)
129
+ # nil or symbolic resource types:
130
+ # apply generic unrestricted permission to the resource_type
131
+ cancan_ability_object.send( action, ability, resource_type )
132
+ next
133
+ else
134
+ # model-based resource types:
135
+ # skip if we cannot get a model instance
136
+ next unless model_resource
137
+ end
138
+
139
+ if permission.resource_id.nil?
140
+
141
+ if action == :cannot
142
+ # apply generic unrestricted permission to the class
143
+ cancan_ability_object.send( action, ability, resource_type )
144
+ else
145
+
146
+ refinements = Cancannible.refinements.each_with_object([]) do |refinement,memo|
147
+ refinement_attributes = refinement.dup
148
+
149
+ allow_nil = !!(refinement_attributes.delete(:allow_nil))
150
+
151
+ refinement_if_condition = refinement_attributes.delete(:if)
152
+ next if refinement_if_condition.respond_to?(:call) && !refinement_if_condition.call(self,model_resource)
153
+
154
+ refinement_scope = Array(refinement_attributes.delete(:scope))
155
+ next if refinement_scope.present? && !refinement_scope.include?(ability)
156
+
157
+ refinement_except = Array(refinement_attributes.delete(:except))
158
+ next if refinement_except.present? && refinement_except.include?(ability)
159
+
160
+ refinement_attribute_names = refinement_attributes.keys.map{|k| "#{k}" }
161
+ next unless (refinement_attribute_names - model_resource.attribute_names).empty?
162
+
163
+ restriction = {}
164
+ refinement_attributes.each do |key,value|
165
+ if value.is_a?(Symbol)
166
+ if self.respond_to?(value)
167
+ restriction[key] = if allow_nil
168
+ Array(self.send(value)) + [nil]
169
+ else
170
+ self.send(value)
171
+ end
172
+ end
173
+ else
174
+ restriction[key] = value
175
+ end
176
+ end
177
+ memo.push(restriction) if restriction.present?
178
+ end
179
+
180
+ if refinements.empty?
181
+ # apply generic unrestricted permission to the class
182
+ cancan_ability_object.send( action, ability, resource_type )
183
+ else
184
+ refinements.each do |refinement|
185
+ cancan_ability_object.send( action, ability, resource_type, refinement)
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ elsif resource_type.find_by_id(permission.resource_id)
192
+ cancan_ability_object.send( action, ability, resource_type, id: permission.resource_id)
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+
199
+ module Cancannible
200
+ # This module is automatically included into all controllers.
201
+ # It overrides some CanCan ControllerAdditions
202
+ module ControllerAdditions
203
+ def current_ability
204
+ current_user.try(:abilities)
205
+ end
206
+ end
207
+ end
208
+
209
+ if defined? ActionController::Base
210
+ ActionController::Base.class_eval do
211
+ include Cancannible::ControllerAdditions
212
+ end
213
+ end