rabarber 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ae0f6a7f272a6d718ddf591e61b236a20b26da7c7eddc56f838637e6fd16b72
4
- data.tar.gz: 07bb483c2ed0fc8e04b12fab9183d333cddae5ef4d3b3690d98119e1d9d97c5b
3
+ metadata.gz: 77a15b7eb4164c74d70e81d34630d6051915acc2abe35896df8938d9cc53e473
4
+ data.tar.gz: 847f675e00e9a85a158032af7ddc158f3189df78d4e6dae4ce15e2fe5024515e
5
5
  SHA512:
6
- metadata.gz: 8c87020ec8feb37dc344f332843eb49498197edbeab72d3c7b57edfb5e727029627aa5b8289fa506bdcb262d2df47fc378ca3d17f138879b3a64e51e998d22ab
7
- data.tar.gz: d4e83ff02a8dfc17e8a2706756fa84f4e93035bd09c98455e8f5f2eb92f9c48636135200ed42fe6b6a6bda94671c187de97c1a85d6a21425d93191423c38711f
6
+ metadata.gz: 7d16835ed84fd9ee2c2e7871080c5256bad34944ee0c1a3448c609f14732f1c7901539c0287bc5206feddb79ef77ee4aa001dd26a642d7c23b05498f5a668659
7
+ data.tar.gz: 0bd35329a6608d4f6680b4aba7882d015df36f38e0b0d963c9df988edd40e490e67180e504b1901f05c957e27f8bffe585372f6494ccad2989310daaa7feec68
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 1.3.0
2
+
3
+ - Add methods to directly add, rename, and remove roles
4
+ - `HasRoles#assign_roles` and `HasRoles#revoke_roles` methods now return the list of roles assigned to the user
5
+ - Minor performance improvements
6
+
7
+ ## 1.2.2
8
+
9
+ - Refactor to improve readability and maintainability
10
+ - Fix minor code errors
11
+
1
12
  ## 1.2.1
2
13
 
3
14
  - Cache roles to avoid unnecessary database queries
data/README.md CHANGED
@@ -67,16 +67,16 @@ Rabarber can be configured by using `.configure` method in an initializer:
67
67
 
68
68
  ```rb
69
69
  Rabarber.configure do |config|
70
- config.cache_enabled = false
71
- config.current_user_method = :authenticated_user
72
- config.must_have_roles = true
70
+ config.cache_enabled = true
71
+ config.current_user_method = :current_user
72
+ config.must_have_roles = false
73
73
  config.when_actions_missing = -> (missing_actions, context) { ... }
74
74
  config.when_roles_missing = -> (missing_roles, context) { ... }
75
75
  config.when_unauthorized = -> (controller) { ... }
76
76
  end
77
77
  ```
78
78
 
79
- - `cache_enabled` must be a boolean determining whether roles are cached. Roles are cached by default to avoid unnecessary database queries. If you want to disable caching, set this option to `false`. If caching is enabled and you need to clear the cache, use the `Rabarber::Cache.clear` method.
79
+ - `cache_enabled` must be a boolean determining whether roles are cached. Roles are cached by default to avoid unnecessary database queries. If you want to disable caching, set this option to `false`. If caching is enabled and you need to clear the cache, use `Rabarber::Cache.clear` method.
80
80
 
81
81
  - `current_user_method` must be a symbol representing the method that returns the currently authenticated user. The default value is `:current_user`.
82
82
 
@@ -112,6 +112,7 @@ By default, `#assign_roles` method will automatically create any roles that don'
112
112
  ```rb
113
113
  user.assign_roles(:accountant, :marketer, create_new: false)
114
114
  ```
115
+ The method returns an array of roles assigned to the user.
115
116
 
116
117
  **`#revoke_roles`**
117
118
 
@@ -122,6 +123,8 @@ user.revoke_roles(:accountant, :marketer)
122
123
  ```
123
124
  If any of the specified roles doesn't exist or the user doesn't have the role you want to revoke, it will be ignored.
124
125
 
126
+ The method returns an array of roles assigned to the user.
127
+
125
128
  **`#has_role?`**
126
129
 
127
130
  To check whether the user has a role, use:
@@ -140,6 +143,51 @@ To view all the roles assigned to the user, use:
140
143
  user.roles
141
144
  ```
142
145
 
146
+ ---
147
+
148
+ To manipulate roles directly, you can use `Rabarber::Role` methods:
149
+
150
+ **`.add`**
151
+
152
+ To add a new role, use:
153
+
154
+ ```rb
155
+ Rabarber::Role.add(:admin)
156
+ ```
157
+
158
+ This will create a new role with the specified name and return `true`. If the role already exists, it will return `false`.
159
+
160
+ **`.rename`**
161
+
162
+ To rename a role, use:
163
+
164
+ ```rb
165
+ Rabarber::Role.rename(:admin, :administrator)
166
+ ```
167
+ The first argument is the old name, and the second argument is the new name. This will rename the role and return `true`. If a role with the new name already exists, it will return `false`.
168
+
169
+ The method won't rename the role if it is assigned to any user. To force the rename, use the method with `force: true` argument:
170
+ ```rb
171
+ Rabarber::Role.rename(:admin, :administrator, force: true)
172
+ ```
173
+
174
+ **`.remove`**
175
+
176
+ To remove a role, use:
177
+
178
+ ```rb
179
+ Rabarber::Role.remove(:admin)
180
+ ```
181
+
182
+ This will remove the role and return `true`. If the role doesn't exist, it will return `false`.
183
+
184
+ The method won't remove the role if it is assigned to any user. To force the removal, use the method with `force: true` argument:
185
+ ```rb
186
+ Rabarber::Role.remove(:admin, force: true)
187
+ ```
188
+
189
+ **`.names`**
190
+
143
191
  If you need to list all the role names available in your application, use:
144
192
 
145
193
  ```rb
@@ -218,24 +266,26 @@ end
218
266
 
219
267
  This allows everyone to access `OrdersController` and its children and `index` action in `InvoicesController`. This also extends to scenarios where there is no user present, i.e. when the method responsible for returning the currently authenticated user in your application returns `nil`.
220
268
 
269
+ Be aware that if the user is not authenticated (the method responsible for returning the currently authenticated user in your application returns `nil`), Rabarber will treat this situation as if the user with no roles assigned was authenticated.
270
+
221
271
  If you've set `must_have_roles` setting to `true`, then, only the users with at least one role can have access. This setting can be useful if your requirements are such that users without roles are not allowed to access anything.
222
272
 
223
273
  For more complex cases, Rabarber provides dynamic rules:
224
274
 
225
275
  ```rb
226
276
  class OrdersController < ApplicationController
227
- grant_access if: :user_has_access?
228
- grant_access unless: :user_has_no_access?
277
+ grant_access if: :current_company_accountant?
278
+ grant_access unless: :fired?
229
279
  ...
230
280
 
231
281
  private
232
282
 
233
- def user_has_access?
234
- ...
283
+ def current_company_accountant?
284
+ current_company.accountant == current_user
235
285
  end
236
286
 
237
- def user_has_no_access?
238
- ...
287
+ def fired?
288
+ current_user.fired?
239
289
  end
240
290
  end
241
291
 
@@ -10,16 +10,16 @@ module Rabarber
10
10
  enabled? ? Rails.cache.fetch(key, options, &block) : yield
11
11
  end
12
12
 
13
- def delete(key)
14
- Rails.cache.delete(key) if enabled?
13
+ def delete(*keys)
14
+ Rails.cache.delete_multi(keys) if enabled?
15
15
  end
16
16
 
17
17
  def enabled?
18
18
  Rabarber::Configuration.instance.cache_enabled
19
19
  end
20
20
 
21
- def key_for(record)
22
- "rabarber:roles_#{record.public_send(record.class.primary_key)}"
21
+ def key_for(id)
22
+ "rabarber:roles_#{id}"
23
23
  end
24
24
 
25
25
  def clear
@@ -78,13 +78,13 @@ module Rabarber
78
78
  -> (missing_roles, context) {
79
79
  delimiter = context[:action] ? "#" : ""
80
80
  message = "Missing roles: #{missing_roles}, context: #{context[:controller]}#{delimiter}#{context[:action]}"
81
- Rails.logger.tagged("Rabarber") { Rails.logger.warn message }
81
+ Rabarber::Logger.log(:warn, message)
82
82
  }
83
83
  end
84
84
 
85
85
  def default_when_unauthorized
86
86
  -> (controller) do
87
- Rails.logger.tagged("Rabarber") { Rails.logger.warn "Unauthorized attempt" }
87
+ Rabarber::Logger.log(:warn, "Unauthorized attempt")
88
88
  if controller.request.format.html?
89
89
  controller.redirect_back fallback_location: controller.main_app.root_path
90
90
  else
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Logger
5
+ module_function
6
+
7
+ def log(log_level, message)
8
+ Rails.logger.tagged("Rabarber") { Rails.logger.public_send(log_level, message) }
9
+ end
10
+ end
11
+ end
@@ -41,7 +41,7 @@ module Rabarber
41
41
 
42
42
  def controller_rules
43
43
  if controller
44
- { controller => Rabarber::Permissions.controller_rules[controller] }
44
+ Rabarber::Permissions.controller_rules.slice(controller)
45
45
  else
46
46
  Rabarber::Permissions.controller_rules
47
47
  end
@@ -49,7 +49,7 @@ module Rabarber
49
49
 
50
50
  def action_rules
51
51
  if controller
52
- { controller => Rabarber::Permissions.action_rules[controller] }
52
+ Rabarber::Permissions.action_rules.slice(controller)
53
53
  else
54
54
  Rabarber::Permissions.action_rules
55
55
  end
@@ -7,8 +7,8 @@ module Rabarber
7
7
 
8
8
  def check_controller_rules
9
9
  controller_rules.each do |controller, controller_rule|
10
- missing_roles = controller_rule.roles - all_roles if controller_rule.present?
11
- missing_list << Rabarber::Missing::Item.new(missing_roles, controller, nil) if missing_roles.present?
10
+ missing_roles = controller_rule.roles - all_roles
11
+ missing_list << Rabarber::Missing::Item.new(missing_roles, controller, nil) unless missing_roles.empty?
12
12
  end
13
13
  end
14
14
 
@@ -17,7 +17,7 @@ module Rabarber
17
17
  end
18
18
 
19
19
  def roles
20
- Rabarber::Cache.fetch(Rabarber::Cache.key_for(self), expires_in: 1.hour, race_condition_ttl: 5.seconds) do
20
+ Rabarber::Cache.fetch(Rabarber::Cache.key_for(roleable_id), expires_in: 1.hour, race_condition_ttl: 5.seconds) do
21
21
  rabarber_roles.names
22
22
  end
23
23
  end
@@ -31,15 +31,25 @@ module Rabarber
31
31
 
32
32
  create_new_roles(roles_to_assign) if create_new
33
33
 
34
- rabarber_roles << Rabarber::Role.where(name: roles_to_assign) - rabarber_roles
34
+ new_roles = Rabarber::Role.where(name: roles_to_assign) - rabarber_roles
35
35
 
36
- delete_cache
36
+ if new_roles.any?
37
+ delete_roleable_cache
38
+ rabarber_roles << new_roles
39
+ end
40
+
41
+ roles
37
42
  end
38
43
 
39
44
  def revoke_roles(*role_names)
40
- self.rabarber_roles = rabarber_roles - Rabarber::Role.where(name: process_role_names(role_names))
45
+ new_roles = rabarber_roles - Rabarber::Role.where(name: process_role_names(role_names))
46
+
47
+ if rabarber_roles != new_roles
48
+ delete_roleable_cache
49
+ self.rabarber_roles = new_roles
50
+ end
41
51
 
42
- delete_cache
52
+ roles
43
53
  end
44
54
 
45
55
  private
@@ -53,8 +63,12 @@ module Rabarber
53
63
  Rabarber::Input::Roles.new(role_names).process
54
64
  end
55
65
 
56
- def delete_cache
57
- Rabarber::Cache.delete(Rabarber::Cache.key_for(self))
66
+ def delete_roleable_cache
67
+ Rabarber::Cache.delete(Rabarber::Cache.key_for(roleable_id))
68
+ end
69
+
70
+ def roleable_id
71
+ public_send(self.class.primary_key)
58
72
  end
59
73
  end
60
74
  end
@@ -6,16 +6,66 @@ module Rabarber
6
6
 
7
7
  validates :name, presence: true, uniqueness: true, format: { with: Rabarber::Input::Roles::REGEX }
8
8
 
9
- after_commit :delete_cache
9
+ has_and_belongs_to_many :roleables, join_table: "rabarber_roles_roleables"
10
10
 
11
- def self.names
12
- pluck(:name).map(&:to_sym)
13
- end
11
+ class << self
12
+ def names
13
+ pluck(:name).map(&:to_sym)
14
+ end
15
+
16
+ def add(name)
17
+ name = process_role_name(name)
18
+
19
+ return false if exists?(name: name)
20
+
21
+ delete_roles_cache
22
+
23
+ !!create!(name: name)
24
+ end
25
+
26
+ def rename(old_name, new_name, force: false)
27
+ role = find_by(name: process_role_name(old_name))
28
+ name = process_role_name(new_name)
29
+
30
+ return false if !role || exists?(name: name) || assigned_to_roleables(role).any? && !force
31
+
32
+ delete_roles_cache
33
+ delete_roleables_cache(role)
34
+
35
+ role.update!(name: name)
36
+ end
37
+
38
+ def remove(name, force: false)
39
+ role = find_by(name: process_role_name(name))
40
+
41
+ return false if !role || assigned_to_roleables(role).any? && !force
42
+
43
+ delete_roles_cache
44
+ delete_roleables_cache(role)
45
+
46
+ !!role.destroy!
47
+ end
48
+
49
+ private
50
+
51
+ def delete_roles_cache
52
+ Rabarber::Cache.delete(Rabarber::Cache::ALL_ROLES_KEY)
53
+ end
54
+
55
+ def delete_roleables_cache(role)
56
+ keys = assigned_to_roleables(role).map { |roleable_id| Rabarber::Cache.key_for(roleable_id) }
57
+ Rabarber::Cache.delete(*keys) if keys.any?
58
+ end
14
59
 
15
- private
60
+ def assigned_to_roleables(role)
61
+ ActiveRecord::Base.connection.select_values(
62
+ "SELECT roleable_id FROM rabarber_roles_roleables WHERE role_id = #{role.id}"
63
+ )
64
+ end
16
65
 
17
- def delete_cache
18
- Rabarber::Cache.delete(Rabarber::Cache::ALL_ROLES_KEY)
66
+ def process_role_name(name)
67
+ Rabarber::Input::Roles.new(name).process[0]
68
+ end
19
69
  end
20
70
  end
21
71
  end
@@ -15,22 +15,24 @@ module Rabarber
15
15
  @storage = { controller_rules: Hash.new({}), action_rules: Hash.new([]) }
16
16
  end
17
17
 
18
- def self.add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
19
- rule = Rabarber::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
20
-
21
- if action
22
- instance.storage[:action_rules][controller] += [rule]
23
- else
24
- instance.storage[:controller_rules][controller] = rule
18
+ class << self
19
+ def add(controller, action, roles, dynamic_rule, negated_dynamic_rule)
20
+ rule = Rabarber::Rule.new(action, roles, dynamic_rule, negated_dynamic_rule)
21
+
22
+ if action
23
+ instance.storage[:action_rules][controller] += [rule]
24
+ else
25
+ instance.storage[:controller_rules][controller] = rule
26
+ end
25
27
  end
26
- end
27
28
 
28
- def self.controller_rules
29
- instance.storage[:controller_rules]
30
- end
29
+ def controller_rules
30
+ instance.storage[:controller_rules]
31
+ end
31
32
 
32
- def self.action_rules
33
- instance.storage[:action_rules]
33
+ def action_rules
34
+ instance.storage[:action_rules]
35
+ end
34
36
  end
35
37
  end
36
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- VERSION = "1.2.1"
4
+ VERSION = "1.3.0"
5
5
  end
data/lib/rabarber.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "rabarber/version"
4
+ require_relative "rabarber/logger"
4
5
  require_relative "rabarber/configuration"
5
6
 
6
7
  require "active_record"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabarber
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-02-07 00:00:00.000000000 Z
12
+ date: 2024-02-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -50,6 +50,7 @@ files:
50
50
  - lib/rabarber/input/types/booleans.rb
51
51
  - lib/rabarber/input/types/procs.rb
52
52
  - lib/rabarber/input/types/symbols.rb
53
+ - lib/rabarber/logger.rb
53
54
  - lib/rabarber/missing/actions.rb
54
55
  - lib/rabarber/missing/base.rb
55
56
  - lib/rabarber/missing/roles.rb
@@ -79,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  - !ruby/object:Gem::Version
80
81
  version: '0'
81
82
  requirements: []
82
- rubygems_version: 3.2.33
83
+ rubygems_version: 3.3.26
83
84
  signing_key:
84
85
  specification_version: 4
85
86
  summary: Simple authorization library for Ruby on Rails.