access_checker 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e5456d21655f8835826452eef2cdac90fa87f1eb
4
+ data.tar.gz: ebd45557dee969efa672eab0145adae09a395574
5
+ SHA512:
6
+ metadata.gz: 06c5baa53ad4ba070432132ddd295812d1c1ef73fb58e99df38f75585ccd4a060be64fe9d4df3b45615fe96e0d4ee4cd697f8787ed50ce6e99d664fa9068fdb5
7
+ data.tar.gz: 1a86f0b714bd1173ad30954dcdbac8900504a53af2d4d756f1c422d65d751d204d1a3b94c64e174ffb3ab6facb537aba9289ddf93c4adcfcdcf469b766fd42ed
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in access_checker.gemspec
4
+ gemspec
@@ -0,0 +1,142 @@
1
+ # AccessChecker
2
+
3
+ AccessChecker is a simple replacement for ACL9, a role-based authentication gem.
4
+ AccessChecker is primarily oriented for functioning as a before\_action 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. AccessChecker (current version) assumes only one role
10
+ per user. AccessChecker requires a _current\_user_ method accessible at the controller level
11
+ and which returns a User object.
12
+
13
+ AccessChecker 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 access_checker): roles, roles\_users (join table)
19
+
20
+
21
+ ### Defaults assumed
22
+
23
+ **User** is the subject model:
24
+ indicate by inserting the following macro after the class definition:
25
+
26
+ ```
27
+ acts_as_authorization_subject
28
+ ```
29
+
30
+ **Role** is the role model:
31
+ indicate by inserting the following macro after the class definition:
32
+
33
+ ```
34
+ acts_as_authorization_role
35
+ ```
36
+
37
+ Note: the gem allows the default model/table names to be changed, but I haven't built tests
38
+ for verifying that the changes will work.
39
+
40
+ ```
41
+ class Role < ActiveRecord::Base
42
+ acts_as_authorization_role
43
+ end
44
+
45
+ class User < ActiveRecord::Base
46
+ acts_as_authorization_subject
47
+ end
48
+ ```
49
+
50
+ ### Migrations required
51
+
52
+ The roles table must be created (sorry, no generator yet)
53
+ Some of the fields are legacy from acl9 and currently are not used.
54
+
55
+ ```
56
+ create_table "roles", :force => true do |t|
57
+ t.string "name", :limit => 40
58
+ t.string "authorizable_type", :limit => 40
59
+ t.string "authorizable_id"
60
+ t.boolean "system", :default=>false
61
+ t.datetime "created_at"
62
+ t.datetime "updated_at"
63
+ end
64
+ ```
65
+
66
+ and the join table
67
+
68
+ ```
69
+ create_table :roles_users, :id => false, :force => true do |t|
70
+ t.column :user_id, :integer
71
+ t.column :role_id, :integer
72
+ end
73
+ add_index :roles_users, :user_id
74
+ ```
75
+
76
+
77
+ ## Usage
78
+
79
+ At the head of every controller for which you wish to control user access,
80
+ you'll need to place an _access_control_ macro which is used to generate a
81
+ before_action for the authorization checks. The macro takes a hash of role
82
+ specification parameters. These must be specified prior to the macro and
83
+ then referenced as the macro's parameter parameter. I cannot be specified
84
+ in-line because Rails is treating it as a before_action and so expects a
85
+ before_action type of ( :only =>, :except => ) hash which would then apply
86
+ to the before_action itself and thus bypass any explicit checking.
87
+
88
+ Unauthorized access yields an exception: AccessChecker::AccessDenied .
89
+ Syntax errors in formulating the control parameters will also raise an exception: AccessChecker::SyntaxError .
90
+ You'll probably want to catch those exceptions and handle them gracefully, probably in your ApplicationController.
91
+
92
+ Notice that roles, limit_types, and controller actions are all expected to be symbols.
93
+
94
+ You have complete freedom to define roles to be whatever you want: except for the reserved words: :all, :anonymous.
95
+ The usage of :all, :anonymous is explained with the following logic.
96
+
97
+ * if current_user.nil?, then proceed as :anonymous if :anonymous is referenced in the role_control_hash
98
+ * if user's role is not referenced, then proceed as :all if :all is referenced in the role_control_hash
99
+ * else proceed and evaluate the role_control_hash with user's role
100
+
101
+ This means that :all will **only** be invoked if a user has a role which is **NOT** specified in the role_control_hash and
102
+ if :all **is** specified in the hash. In that sense :unspecified would be more accurate than :all, but :all is shorter and
103
+ handier to work with.
104
+ And it means that :anonymous will **only** be invoked if current_user.nil? is true and
105
+ if :anonymous **is** specified in the hash.
106
+
107
+ The access limitation types are:
108
+
109
+ * :allow, :to, :only -- control what access is allowed; all else will be denied
110
+ * :deny, :except -- control what is denied; all else will be permitted
111
+
112
+ The action list can be empty; in which case, for :allow, all actions are permitted; and for :deny, no actions are permitted.
113
+ If both limit_type and action_list are missing, then :allow => [] will be assumed.
114
+
115
+ ```
116
+ class AnyController < ApplicationController
117
+
118
+ control_parameters = {
119
+ :admin => { :allow => [] },
120
+ :manager => { :deny => [ :delete, :edit ] },
121
+ :member => { :allow => [ :index, :show ] },
122
+ :anonymous => { :allow => [:index ] },
123
+ :all => { :deny => [:edit, :update] }
124
+ }
125
+
126
+ access_control control_parameters
127
+ ```
128
+
129
+ Catch exceptions:
130
+
131
+ ```
132
+ class ApplicationController < ActionController::Base
133
+ rescue_from AccessChecker::AccessDenied do |e|
134
+ # do something here
135
+ end
136
+ ```
137
+
138
+ ## Acknowledgements
139
+
140
+ AccessChecker was influenced by the acl9 gem (https://github.com/be9/acl9).
141
+
142
+
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require "bundler/gem_tasks"
3
+ require 'bundler'
4
+
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
+
13
+ require 'rake'
14
+
15
+ # ------------------------------------------------------------------------
16
+ # don't use normal Rake test routine because of double factory_bot
17
+ # ------------------------------------------------------------------------
18
+ task :test do
19
+ ruby '-I test "test/test_access_checker.rb"'
20
+ end # test task
21
+
22
+ task :default => :test
23
+
24
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "access_checker"
3
+ s.version = "0.0.2"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Markus Hediger"]
7
+ s.date = "2018-01-01"
8
+ s.description = "Simple access control on controller basis"
9
+ s.email = "m.hed@gmx.ch"
10
+ s.files = [
11
+ "Gemfile",
12
+ "README.md",
13
+ "Rakefile",
14
+ "VERSION",
15
+ "lib/access_checker/access_control.rb",
16
+ "lib/access_checker/base.rb",
17
+ "lib/access_checker/control.rb",
18
+ "lib/access_checker/subject_extensions.rb",
19
+ "lib/access_checker.rb",
20
+ "access_checker.gemspec"
21
+ ]
22
+ s.homepage = "http://github.com/kusihed/access_checker"
23
+ s.licenses = ["MIT"]
24
+ s.require_paths = ["lib"]
25
+ s.summary = "Simple access control on controller basis"
26
+
27
+ s.add_development_dependency "rails", "~> 5.0"
28
+ s.add_development_dependency "bundler"
29
+ s.add_development_dependency "rake"
30
+ s.add_development_dependency "sqlite3"
31
+ s.add_development_dependency "test-unit"
32
+ s.add_development_dependency "factory_bot"
33
+ s.add_development_dependency "shoulda"
34
+ s.add_development_dependency "turn"
35
+ end
36
+
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/access_checker/base'
2
+ require File.dirname(__FILE__) + '/access_checker/control'
3
+ require File.dirname(__FILE__) + '/access_checker/subject_extensions'
4
+ require File.dirname(__FILE__) + '/access_checker/access_control'
5
+
6
+ module AccessChecker
7
+ @@configurator = {
8
+ :default_role_class_name => 'Role',
9
+ :default_subject_class_name => 'User',
10
+ :default_subject_method => :current_user,
11
+ :default_roles_collection_name => :roles,
12
+ :default_users_collection_name => :users,
13
+ :default_join_table_name => "roles_users"
14
+ }
15
+
16
+ mattr_reader :configurator
17
+
18
+ class AccessDenied < SecurityError; end
19
+ class EmptyRoles < RuntimeError; end
20
+ class SyntaxError < ArgumentError; end
21
+ end
22
+
23
+ # coding: utf-8
24
+ ActiveRecord::Base.class_eval do
25
+ include AccessChecker::Base
26
+ end
27
+ ActionController::Base.class_eval do
28
+ include AccessChecker::Control
29
+ end
30
+
@@ -0,0 +1,83 @@
1
+ module AccessChecker
2
+ class AccessControl < Struct.new( :role_control_hash )
3
+
4
+ # ------------------------------------------------------------------------------
5
+ # EXCEPTION:
6
+ # AccessChecker::AccessDenied -- execution denied
7
+ # AccessChecker::SyntaxError -- unexpected limit_type, or action_type
8
+ # #############################################################################
9
+
10
+ # ---------------------------------------------------------------------------------
11
+ # possible states and resultant handlings
12
+ # limit_type action_list permitted handling
13
+ # ---------------------------------------------------------------------------------
14
+ # :allow, :to, :only [] empty, action matched permitted
15
+ # action not matched denied
16
+ #
17
+ # :deny, :except [] empty, action matched denied
18
+ # action not matched permitted
19
+ #
20
+ # ---------------------------------------------------------------------------------
21
+ # if current_user.nil?, proceed as :anonymous if so referenced in role_control_hash
22
+ # if user's role is not referenced, proceed as :all if so referenced in role_control_hash
23
+ # else proceed with user's role
24
+ # ------------------------------------------------------------------------------
25
+ def before( controller )
26
+
27
+ if controller.current_user.nil? # no user defined; anonymous permitted?
28
+
29
+ if self.role_control_hash.member?( :anonymous ) # if anonymous is referenced, continue...
30
+ my_role = :anonymous # ...then handle anonymously
31
+ else # unauthorized access of controller
32
+ raise AccessChecker::AccessDenied
33
+ end # if..then..else anonymous check
34
+
35
+ elsif !self.role_control_hash.member?( my_role = controller.current_user.get_role.name.to_sym )
36
+
37
+ if self.role_control_hash.member?( :all ) # if all is referenced, continue...
38
+ my_role = :all # ...then handle as all
39
+ else # unauthorized access of controller
40
+ raise AccessChecker::AccessDenied
41
+ end # if..then..else anonymous check
42
+
43
+ end # if..elsif check for anonymous or role not allowed
44
+
45
+ expected_action = controller.action_name.to_sym # action being attempted
46
+
47
+ permitted = true # presume authorized
48
+
49
+ # now check the action_hash for action access
50
+ # shown as a loop, but only the first entry is meaningful
51
+ self.role_control_hash[my_role].each do |limit_type, action_list|
52
+
53
+ unless action_list.kind_of?( Array )
54
+ raise AccessChecker::SyntaxError, "all action lists should be arrays of symbols"
55
+ end
56
+
57
+ permitted = ( action_list.empty? || action_list.include?( expected_action ) )
58
+
59
+ case limit_type
60
+ when :allow, :to, :only then permitted
61
+ when :deny, :except then permitted = !permitted
62
+ else
63
+ raise AccessChecker::SyntaxError, "Unrecognized access_control limit_type: #{limit_type}"
64
+ end # case
65
+
66
+
67
+ unless permitted # ... figure out the type of exception
68
+ if action_list.any?{ |actn| actn.class.name != "Symbol" }
69
+ raise AccessChecker::SyntaxError, "all actions should be symbols"
70
+ else
71
+ raise AccessChecker::AccessDenied
72
+ end # syntax checking
73
+ end # unless
74
+
75
+ break # always break the loop at success
76
+
77
+ end # do check if role
78
+
79
+ return permitted
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,64 @@
1
+ module AccessChecker
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 AccessChecker::ModelExtensions::Subject#has_role!
15
+ # @see AccessChecker::ModelExtensions::Subject#has_role?
16
+ # @see AccessChecker::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] || AccessChecker::configurator[:default_roles_collection_name]
27
+ role = options[:role_class_name] || AccessChecker::configurator[:default_role_class_name]
28
+ join_table = options[:join_table_name] || AccessChecker::configurator[: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 AccessChecker::SubjectExtensions
39
+
40
+ end
41
+
42
+
43
+ def acts_as_authorization_role(options = {})
44
+
45
+ assoc = options[:association_name] || AccessChecker::configurator[:default_users_collection_name]
46
+ subject = options[:subject_class_name] || AccessChecker::configurator[:default_subject_class_name]
47
+ join_table = options[:join_table_name] || AccessChecker::configurator[:default_join_table_name]
48
+
49
+ has_and_belongs_to_many assoc, :class_name => subject, :join_table => join_table
50
+
51
+ cattr_accessor :_auth_role_class_name, :_auth_subject_class_name, :_auth_role_assoc_name
52
+
53
+ self._auth_role_class_name = self.to_s
54
+ self._auth_subject_class_name = subject
55
+ self._auth_role_assoc_name = assoc
56
+
57
+ scope :named_role, lambda { |role_name| where(["name = ?", role_name.to_s]) }
58
+
59
+ end
60
+
61
+ end # module ClassMethods
62
+
63
+ end # module Base
64
+ end
@@ -0,0 +1,14 @@
1
+ module AccessChecker
2
+ module Control
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def access_control( role_control_hash, options = {} )
10
+ before_action AccessChecker::AccessControl.new( role_control_hash ), options
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,68 @@
1
+ module AccessChecker
2
+ module SubjectExtensions
3
+
4
+ # ------------------------------------------------------------------------------
5
+ # has_role? -- returns true if subject has the given role
6
+ # ------------------------------------------------------------------------------
7
+ def has_role?(role_name)
8
+ !get_role( role_name ).nil?
9
+ end
10
+
11
+ # ------------------------------------------------------------------------------
12
+ # has_role! -- forces subject to have the given role
13
+ # ------------------------------------------------------------------------------
14
+ def has_role!(role_name)
15
+ role = _auth_role_class.where( :name => role_name.to_s ). # acts as the find part
16
+ first_or_create( :name => role_name.to_s ) # acts as the create part
17
+ role_objects << role unless self.role_objects.member?(role)
18
+ role
19
+ end
20
+
21
+ # ------------------------------------------------------------------------------
22
+ # remove_role! -- foreces subject to NOT have the given role
23
+ # ------------------------------------------------------------------------------
24
+ def remove_role!(role_name)
25
+ role_objects.delete( get_role( role_name ) )
26
+ end
27
+
28
+ # ------------------------------------------------------------------------------
29
+ # get_role -- returns a role obj for subject; else nil
30
+ # EXCEPTION: EmptyRolesException if role_objects collection is empty
31
+ # ------------------------------------------------------------------------------
32
+ def get_role( role_name=nil )
33
+
34
+ raise AccessChecker::EmptyRoles if role_objects.empty?
35
+
36
+ if role_name.nil?
37
+ role_objects.first
38
+ else
39
+ role_objects.where( :name => role_name.to_s ).first
40
+ end
41
+
42
+ end
43
+
44
+ protected
45
+
46
+ # ------------------------------------------------------------------------------
47
+ # _auth_role_class -- retuns the Klass for the Role model
48
+ # ------------------------------------------------------------------------------
49
+ def _auth_role_class
50
+ self.class._auth_role_class_name.constantize
51
+ end
52
+
53
+ # ------------------------------------------------------------------------------
54
+ # _auth_role_assoc -- returns the habtm symbol for the array of subject.roles
55
+ # ------------------------------------------------------------------------------
56
+ def _auth_role_assoc
57
+ self.class._auth_role_assoc_name
58
+ end
59
+
60
+ # ------------------------------------------------------------------------------
61
+ # role_objects -- returns the habtm array of roles for the subject
62
+ # ------------------------------------------------------------------------------
63
+ def role_objects
64
+ send(self._auth_role_assoc)
65
+ end
66
+
67
+ end
68
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: access_checker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Markus Hediger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: factory_bot
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: shoulda
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: turn
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Simple access control on controller basis
126
+ email: m.hed@gmx.ch
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - Gemfile
132
+ - README.md
133
+ - Rakefile
134
+ - VERSION
135
+ - access_checker.gemspec
136
+ - lib/access_checker.rb
137
+ - lib/access_checker/access_control.rb
138
+ - lib/access_checker/base.rb
139
+ - lib/access_checker/control.rb
140
+ - lib/access_checker/subject_extensions.rb
141
+ homepage: http://github.com/kusihed/access_checker
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.6.13
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Simple access control on controller basis
165
+ test_files: []