access_checker 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []