kibali 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem 'rails', '~> 3.2.8'
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development, :test do
10
+ gem "rdoc", "~> 3.12"
11
+ gem "jeweler", "~> 1.8.4"
12
+ gem 'sqlite3'
13
+ end
14
+
15
+ group :test do
16
+ gem "factory_girl"
17
+ gem "shoulda"
18
+ # Pretty printed test output
19
+ gem 'turn', :require => false
20
+ end
21
+
22
+
data/Gemfile.lock ADDED
@@ -0,0 +1,110 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (3.2.9)
5
+ actionpack (= 3.2.9)
6
+ mail (~> 2.4.4)
7
+ actionpack (3.2.9)
8
+ activemodel (= 3.2.9)
9
+ activesupport (= 3.2.9)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ journey (~> 1.0.4)
13
+ rack (~> 1.4.0)
14
+ rack-cache (~> 1.2)
15
+ rack-test (~> 0.6.1)
16
+ sprockets (~> 2.2.1)
17
+ activemodel (3.2.9)
18
+ activesupport (= 3.2.9)
19
+ builder (~> 3.0.0)
20
+ activerecord (3.2.9)
21
+ activemodel (= 3.2.9)
22
+ activesupport (= 3.2.9)
23
+ arel (~> 3.0.2)
24
+ tzinfo (~> 0.3.29)
25
+ activeresource (3.2.9)
26
+ activemodel (= 3.2.9)
27
+ activesupport (= 3.2.9)
28
+ activesupport (3.2.9)
29
+ i18n (~> 0.6)
30
+ multi_json (~> 1.0)
31
+ ansi (1.4.3)
32
+ arel (3.0.2)
33
+ builder (3.0.4)
34
+ erubis (2.7.0)
35
+ factory_girl (4.1.0)
36
+ activesupport (>= 3.0.0)
37
+ git (1.2.5)
38
+ hike (1.2.1)
39
+ i18n (0.6.1)
40
+ jeweler (1.8.4)
41
+ bundler (~> 1.0)
42
+ git (>= 1.2.5)
43
+ rake
44
+ rdoc
45
+ journey (1.0.4)
46
+ json (1.7.5)
47
+ mail (2.4.4)
48
+ i18n (>= 0.4.0)
49
+ mime-types (~> 1.16)
50
+ treetop (~> 1.4.8)
51
+ mime-types (1.19)
52
+ multi_json (1.3.7)
53
+ polyglot (0.3.3)
54
+ rack (1.4.1)
55
+ rack-cache (1.2)
56
+ rack (>= 0.4)
57
+ rack-ssl (1.3.2)
58
+ rack
59
+ rack-test (0.6.2)
60
+ rack (>= 1.0)
61
+ rails (3.2.9)
62
+ actionmailer (= 3.2.9)
63
+ actionpack (= 3.2.9)
64
+ activerecord (= 3.2.9)
65
+ activeresource (= 3.2.9)
66
+ activesupport (= 3.2.9)
67
+ bundler (~> 1.0)
68
+ railties (= 3.2.9)
69
+ railties (3.2.9)
70
+ actionpack (= 3.2.9)
71
+ activesupport (= 3.2.9)
72
+ rack-ssl (~> 1.3.2)
73
+ rake (>= 0.8.7)
74
+ rdoc (~> 3.4)
75
+ thor (>= 0.14.6, < 2.0)
76
+ rake (10.0.2)
77
+ rdoc (3.12)
78
+ json (~> 1.4)
79
+ shoulda (3.3.2)
80
+ shoulda-context (~> 1.0.1)
81
+ shoulda-matchers (~> 1.4.1)
82
+ shoulda-context (1.0.1)
83
+ shoulda-matchers (1.4.1)
84
+ activesupport (>= 3.0.0)
85
+ sprockets (2.2.1)
86
+ hike (~> 1.2)
87
+ multi_json (~> 1.0)
88
+ rack (~> 1.0)
89
+ tilt (~> 1.1, != 1.3.0)
90
+ sqlite3 (1.3.6)
91
+ thor (0.16.0)
92
+ tilt (1.3.3)
93
+ treetop (1.4.12)
94
+ polyglot
95
+ polyglot (>= 0.3.1)
96
+ turn (0.9.6)
97
+ ansi
98
+ tzinfo (0.3.35)
99
+
100
+ PLATFORMS
101
+ ruby
102
+
103
+ DEPENDENCIES
104
+ factory_girl
105
+ jeweler (~> 1.8.4)
106
+ rails (~> 3.2.8)
107
+ rdoc (~> 3.12)
108
+ shoulda
109
+ sqlite3
110
+ turn
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Daudi Amani
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.md ADDED
@@ -0,0 +1,183 @@
1
+ # kibali
2
+
3
+ Kibali is a simple replacement for ACL9, a role-based authentication gem.
4
+ Kibali is primarily oriented for functioning as a before\_filter role authentication
5
+ scheme for Rails controllers.
6
+
7
+ ## Basic concepts
8
+ Authentication is often called for on a controller-by-controller basis, restricting
9
+ actions to users who possess certain roles. Kibali (current version) assumes only one role
10
+ per user. Kibali requires a _current\_user_ method accessible at the controller level
11
+ and which returns a User object.
12
+
13
+ Kibali adds, to the User model, role accessor methods: has\_role?, has\_role!, get\_role
14
+
15
+ ## Structure
16
+
17
+ * necessary models: user, roles, roles\_users (join table)
18
+ * necessary migrations (for kibali): roles, roles\_users (join table)
19
+
20
+ ## Dependency requirements
21
+
22
+ * Rails 3.2 or higher
23
+
24
+ ## Installation
25
+
26
+ Either install the gem manually:
27
+
28
+ ```
29
+ $ gem install
30
+ ```
31
+
32
+ Or use bundler and place in your Gemfile
33
+
34
+ ```
35
+ gem 'kibali'
36
+ ```
37
+
38
+ ## Setup
39
+
40
+ ### Defaults assumed
41
+
42
+ **User** is the subject model:
43
+ indicate by inserting the following macro after the class definition:
44
+
45
+ ```
46
+ acts_as_authorization_subject
47
+ ```
48
+
49
+ **Role** is the role model:
50
+ indicate by inserting the following macro after the class definition:
51
+
52
+ ```
53
+ acts_as_authorization_role
54
+ ```
55
+
56
+ Note: the gem allows the default model/table names to be changed, but I haven't built tests
57
+ for verifying that the changes will work.
58
+
59
+ ```
60
+ class Role < ActiveRecord::Base
61
+ acts_as_authorization_role
62
+ end
63
+
64
+ class User < ActiveRecord::Base
65
+ acts_as_authorization_subject
66
+ end
67
+ ```
68
+
69
+ ### Migrations required
70
+
71
+ The roles table must be created (sorry, no generator yet)
72
+ Some of the fields are legacy from acl9 and currently are not used.
73
+
74
+ ```
75
+ create_table "roles", :force => true do |t|
76
+ t.string "name", :limit => 40
77
+ t.string "authorizable_type", :limit => 40
78
+ t.string "authorizable_id"
79
+ t.boolean "system", :default=>false
80
+ t.datetime "created_at"
81
+ t.datetime "updated_at"
82
+ end
83
+ ```
84
+
85
+ and the join table
86
+
87
+ ```
88
+ create_table :roles_users, :id => false, :force => true do |t|
89
+ t.column :user_id, :integer
90
+ t.column :role_id, :integer
91
+ end
92
+ add_index :roles_users, :user_id
93
+ ```
94
+
95
+
96
+ ## Usage
97
+
98
+ At the head of every controller for which you wish to control user access,
99
+ you'll need to place an _access_control_ macro which is used to generate a
100
+ before_filter for the authorization checks. In the testing, I had to place the
101
+ parameter in a seperate statement because syntax errors were encountered. That
102
+ might be due to the limited test structure used. I'll show both forms here.
103
+
104
+ Unauthorized access yields an exception: Kibali::AccessDenied .
105
+ Syntax errors in formulating the control parameters will also raise an exception: Kibali::SyntaxError .
106
+ You'll probably want to catch those exceptions and handle them gracefully, probably in your ApplicationController.
107
+
108
+ Notice that roles, limit_types, and controller actions are all expected to be symbols.
109
+
110
+ You have complete freedom to define roles to be whatever you want: except for the reserved words: :all, :anonymous.
111
+
112
+ The access limitation types are:
113
+
114
+ * :allow, :to, :only -- control what access is allowed; all else will be denied
115
+ * :deny, :except -- control what is denied; all else will be permitted
116
+
117
+ The action list can be empty; in which case, for :allow, all actions are permitted; and for :deny, no actions are permitted.
118
+ If both limit_type and action_list are missing, then :allow => [] will be assumed.
119
+
120
+ ```
121
+ class AnyController < ApplicationController
122
+
123
+ access_control {
124
+ :admin => { :allow => [] },
125
+ :manager => { :deny => [ :delete, :edit ] },
126
+ :member => { :allow => [ :index, :show ] }
127
+ }
128
+ ```
129
+
130
+ alternatively
131
+
132
+ ```
133
+ class AnyController < ApplicationController
134
+
135
+ control_parameters = {
136
+ :admin => { :allow => [] },
137
+ :manager => { :deny => [ :delete, :edit ] },
138
+ :member => { :allow => [ :index, :show ] }
139
+ }
140
+ access_control control_parameters
141
+ ```
142
+
143
+ Catch exceptions:
144
+
145
+ ```
146
+ class ApplicationController < ActionController::Base
147
+ rescue_from Kibali::AccessDenied do |e|
148
+ # do something here
149
+ end
150
+ ```
151
+
152
+
153
+ ## Examples
154
+
155
+ Please see the examples in the test folder
156
+
157
+ ## Acknowledgements
158
+
159
+ Kibali was influenced by the acl9 gem (https://github.com/be9/acl9).
160
+ I used acl9 for a number of years until it broke under Rails 3.2.x with a SystemStack error.
161
+ Trying to fix the bug proved to be difficult due to the extent of meta programming. Noting that Rails now
162
+ had a simple way to designate a class to handle before_filters
163
+ (Gavin Morrice's excellent tutorial: http://gavinmorrice.com/blog/posts/15-dryer-neater-rails-before_filters-using-classes),
164
+ I decided that a simpler role-based authorization Rails 3.2.x gem would be valuable. As I already had a production app based
165
+ on acl9, I wanted the conversion to be as seamless as possible. I've kept the same tables and naming. And I've made
166
+ the access_control syntax similar (though not the same). I did keep the same model macro names and config default variables,
167
+ but all access_control code has changed. The tests are all different.
168
+
169
+
170
+ ## Contributing to kibali
171
+
172
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
173
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
174
+ * Fork the project
175
+ * Start a feature/bugfix branch
176
+ * Commit and push until you are happy with your contribution
177
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
178
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
179
+
180
+ ## Copyright
181
+
182
+ Copyright (c) 2012 Daudi Amani. See LICENSE.txt for further details.
183
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "kibali"
18
+ gem.homepage = "http://github.com/dsaronin@gmail.com/kibali"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{rails role authentication}
21
+ gem.description = %Q{simple Rails role authentication}
22
+ gem.email = "dsaronin@gmail.com"
23
+ gem.authors = ["Daudi Amani"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ # ------------------------------------------------------------------------
30
+ # don't use normal Rake test routine because of double factory_girl
31
+ # ------------------------------------------------------------------------
32
+ task :test do
33
+ ruby '-I test "test/test_kibali.rb"'
34
+ ruby '-I test "test/test_access.rb"'
35
+ end # test task
36
+
37
+ task :default => :test
38
+
39
+ require 'rdoc/task'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "kibali #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,89 @@
1
+ module Kibali
2
+ class AccessControl < Struct.new( :role_control_hash )
3
+
4
+ # #############################################################################
5
+ # #############################################################################
6
+
7
+ # ------------------------------------------------------------------------------
8
+ # EXCEPTION:
9
+ # Kibali::AccessDenied -- execution denied
10
+ # Kibali::SyntaxError -- unexpected limit_type, or action_type
11
+ # #############################################################################
12
+ # see test/app/controllers/empty_controller.rb for sample syntax
13
+
14
+ # ---------------------------------------------------------------------------------
15
+ # possible states and resultant handlings
16
+ # limit_type action_list permitted handling
17
+ # ---------------------------------------------------------------------------------
18
+ # :allow, :to, :only [] empty, action matched permitted
19
+ # action not matched denied
20
+ #
21
+ # :deny, :except [] empty, action matched denied
22
+ # action not matched permitted
23
+ #
24
+ # ---------------------------------------------------------------------------------
25
+ # ------------------------------------------------------------------------------
26
+ def before( controller )
27
+
28
+ my_role = controller.current_user.get_role.name.to_sym
29
+
30
+ # if current_user's role not present; check if anonymous is
31
+ unless self.role_control_hash.member?( my_role )
32
+ # here if current_user's role not specificied in control list
33
+
34
+ if self.role_control_hash.member?( :anonymous ) # if anonymous is...
35
+ my_role = :anonymous # ...then handle anonymously
36
+ else # unauthorized access of controller
37
+ raise Kibali::AccessDenied
38
+ end # if..then..else anonymous check
39
+
40
+ end # unless current_user has a role to be checked
41
+
42
+ expected_action = controller.action_name.to_sym # action being attempted
43
+
44
+ permitted = true # presume authorized
45
+
46
+ # now check the action_hash for action access
47
+ # shown as a loop, but only the first entry is meaningful
48
+ self.role_control_hash[my_role].each do |limit_type, action_list|
49
+
50
+ permitted = ( action_list.empty? || action_list.include?( expected_action ) )
51
+
52
+ case limit_type
53
+ when :allow, :to, :only then permitted
54
+ when :deny, :except then permitted = !permitted
55
+ else
56
+ raise Kibali::SyntaxError, "Unrecognized access_control limit_type: #{limit_type}"
57
+ end # case
58
+
59
+
60
+ unless permitted # ... figure out the type of exception
61
+ if action_list.any?{ |actn| actn.class.name != "Symbol" }
62
+ raise Kibali::SyntaxError, "all actions should be symbols"
63
+ else
64
+ raise Kibali::AccessDenied
65
+ end # syntax checking
66
+ end # unless
67
+
68
+ break # always break the loop at success
69
+
70
+ end # do check if role
71
+
72
+ return permitted
73
+ end
74
+
75
+ # ------------------------------------------------------------------------------
76
+ # ------------------------------------------------------------------------------
77
+
78
+ private
79
+
80
+ # ------------------------------------------------------------------------------
81
+ # ------------------------------------------------------------------------------
82
+
83
+ # ------------------------------------------------------------------------------
84
+ # ------------------------------------------------------------------------------
85
+
86
+ # #############################################################################
87
+ # #############################################################################
88
+ end
89
+ end
@@ -0,0 +1,79 @@
1
+ module Kibali
2
+ module Base
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ # #############################################################################
9
+ # @example
10
+ # class Role < ActiveRecord::Base
11
+ # acts_as_authorization_role
12
+ # end
13
+ #
14
+ # @see Kibali::ModelExtensions::Subject#has_role!
15
+ # @see Kibali::ModelExtensions::Subject#has_role?
16
+ # @see Kibali::ModelExtensions::Subject#has_no_role!
17
+ # #############################################################################
18
+
19
+ module ClassMethods
20
+
21
+ # ------------------------------------------------------------------------
22
+ # acts_as_authorization_subject -- designates model to be user subject
23
+ # ------------------------------------------------------------------------
24
+ def acts_as_authorization_subject(options = {})
25
+
26
+ assoc = options[:association_name] || Kibali::config[:default_roles_collection_name]
27
+ role = options[:role_class_name] || Kibali::config[:default_role_class_name]
28
+ join_table = options[:join_table_name] || Kibali::config[:default_join_table_name]
29
+
30
+ has_and_belongs_to_many assoc, :class_name => role, :join_table => join_table
31
+
32
+ cattr_accessor :_auth_role_class_name, :_auth_subject_class_name, :_auth_role_assoc_name
33
+
34
+ self._auth_role_class_name = role
35
+ self._auth_subject_class_name = self.to_s
36
+ self._auth_role_assoc_name = assoc
37
+
38
+ include Kibali::SubjectExtensions
39
+
40
+ end
41
+
42
+ # ------------------------------------------------------------------------
43
+ # ------------------------------------------------------------------------
44
+ def acts_as_authorization_role(options = {})
45
+
46
+ assoc = options[:association_name] || Kibali::config[:default_users_collection_name]
47
+ subject = options[:subject_class_name] || Kibali::config[:default_subject_class_name]
48
+ join_table = options[:join_table_name] || Kibali::config[:default_join_table_name]
49
+
50
+ has_and_belongs_to_many assoc, :class_name => subject, :join_table => join_table
51
+
52
+ cattr_accessor :_auth_role_class_name, :_auth_subject_class_name, :_auth_role_assoc_name
53
+
54
+ self._auth_role_class_name = self.to_s
55
+ self._auth_subject_class_name = subject
56
+ self._auth_role_assoc_name = assoc
57
+
58
+ scope :named_role, lambda { |role_name| where(["name = ?", role_name.to_s]) }
59
+
60
+ end
61
+
62
+ # ------------------------------------------------------------------------
63
+ # ------------------------------------------------------------------------
64
+
65
+ # ------------------------------------------------------------------------
66
+ # ------------------------------------------------------------------------
67
+
68
+ # ------------------------------------------------------------------------
69
+ # ------------------------------------------------------------------------
70
+
71
+ # ------------------------------------------------------------------------
72
+ # ------------------------------------------------------------------------
73
+
74
+ end # module ClassMethods
75
+ # #############################################################################
76
+ # #############################################################################
77
+
78
+ end # module Base
79
+ end # module Kibali
@@ -0,0 +1,39 @@
1
+ module Kibali
2
+ module Control
3
+
4
+ # #############################################################################
5
+ # #############################################################################
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ # #############################################################################
12
+ # #############################################################################
13
+ module ClassMethods
14
+
15
+ def access_control( role_control_hash, options = {} )
16
+ before_filter Kibali::AccessControl.new( role_control_hash ), options
17
+ end
18
+
19
+ end # module ClassMethods
20
+
21
+ # #############################################################################
22
+ # #############################################################################
23
+
24
+ private
25
+
26
+ # ------------------------------------------------------------------------------
27
+ # ------------------------------------------------------------------------------
28
+
29
+ # ------------------------------------------------------------------------------
30
+ # ------------------------------------------------------------------------------
31
+
32
+ # ------------------------------------------------------------------------------
33
+ # ------------------------------------------------------------------------------
34
+
35
+ # #############################################################################
36
+ # #############################################################################
37
+
38
+ end # module Control
39
+ end # module Kibali
@@ -0,0 +1,17 @@
1
+ require 'kibali'
2
+ require 'rails'
3
+
4
+ module Kibali
5
+ class Railtie < Rails::Railtie
6
+ initializer :after_initialize do
7
+
8
+ ActiveRecord::Base.send(:include, Kibali::Base)
9
+ ActionController::Base.send(:include, Kibali::Control)
10
+
11
+ end
12
+
13
+ # rake_tasks do
14
+ # load 'kibali/tasks.rb'
15
+ # end
16
+ end
17
+ end