rabarber 1.2.0 → 1.2.1

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: e7eaa4eaef0f5c6d072e3a5b545b0ce152651df15b521be933aac78e1add85ba
4
- data.tar.gz: d0557fcd900f2bebd58f6489c2f3f79c2f95c7c14bbeeeb59a294fdfcbcb3f49
3
+ metadata.gz: 3ae0f6a7f272a6d718ddf591e61b236a20b26da7c7eddc56f838637e6fd16b72
4
+ data.tar.gz: 07bb483c2ed0fc8e04b12fab9183d333cddae5ef4d3b3690d98119e1d9d97c5b
5
5
  SHA512:
6
- metadata.gz: cadc08751bf39ab0c6eb8a357854fe00bb478533a7f5a7e0760929d6acedad0fcecb31e17c21c980c764d59302a569df74e847f09c778b2e3f1c257c520c4336
7
- data.tar.gz: 7067e81820ce07c0929e3f18ad079c9cfa44b0dc473138b042e4e882b8db1cda17bbc680a431634c42695434a088605fd897ab4a2e25a4d4d2b9bac7c8180fd5
6
+ metadata.gz: 8c87020ec8feb37dc344f332843eb49498197edbeab72d3c7b57edfb5e727029627aa5b8289fa506bdcb262d2df47fc378ca3d17f138879b3a64e51e998d22ab
7
+ data.tar.gz: d4e83ff02a8dfc17e8a2706756fa84f4e93035bd09c98455e8f5f2eb92f9c48636135200ed42fe6b6a6bda94671c187de97c1a85d6a21425d93191423c38711f
data/CHANGELOG.md CHANGED
@@ -1,7 +1,14 @@
1
+ ## 1.2.1
2
+
3
+ - Cache roles to avoid unnecessary database queries
4
+ - Introduce `cache_enabled` configuration option allowing to enable or disable role caching
5
+ - Enhance the migration generator so that it can receive the table name of the model representing users in the application as an argument
6
+ - Various minor improvements
7
+
1
8
  ## 1.2.0
2
9
 
3
- - Enhance handling of missing actions and roles specified in `grant_access` method by raising an error for missing actions and logging a warning for missing roles.
4
- - Introduce `when_actions_missing` and `when_roles_missing` configuration options, allowing to customize the behavior when actions or roles are not found.
10
+ - Enhance handling of missing actions and roles specified in `grant_access` method by raising an error for missing actions and logging a warning for missing roles
11
+ - Introduce `when_actions_missing` and `when_roles_missing` configuration options, allowing to customize the behavior when actions or roles are not found
5
12
 
6
13
  ## 1.1.0
7
14
 
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2023 enjaku4, trafium
3
+ Copyright (c) 2023 enjaku4 (https://github.com/enjaku4), trafium (https://github.com/trafium)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -3,7 +3,9 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/rabarber.svg)](http://badge.fury.io/rb/rabarber)
4
4
  [![Github Actions badge](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml/badge.svg)](https://github.com/enjaku4/rabarber/actions/workflows/ci.yml)
5
5
 
6
- Rabarber is a role-based authorization library for Ruby on Rails, primarily designed for use in the application layer but not limited to that. It offers a set of useful tools for managing user roles and defining authorization rules.
6
+ Rabarber is a role-based authorization library for Ruby on Rails, designed primarily for use in the web layer (specifically controllers and views) but not limited to that. It provides tools for managing user roles and defining authorization rules, mainly focusing on answering the question of 'Who can access which endpoint?'.
7
+
8
+ Unlike some other libraries, Rabarber does not handle data scoping. Instead, it focuses on providing a lightweight and flexible solution for role-based access control, allowing developers to implement data scoping according to their specific business rules directly within their application's code.
7
9
 
8
10
  ---
9
11
 
@@ -45,16 +47,14 @@ Install the gem:
45
47
  bundle install
46
48
  ```
47
49
 
48
- Next, generate a migration to create tables for storing roles in the database:
50
+ Next, generate a migration to create tables for storing roles in the database. Make sure to specify the table name of the model representing users in your application as an argument. For instance, if the table name is `users`, run:
49
51
 
50
52
  ```
51
- rails g rabarber:roles
53
+ rails g rabarber:roles users
52
54
  ```
53
55
 
54
56
  This will create a migration file in `db/migrate` directory.
55
57
 
56
- Replace `raise(Rabarber::Error, "Please specify your user model's table name")` in that file with the name of your user model's table.
57
-
58
58
  Finally, run the migration to apply the changes to the database:
59
59
 
60
60
  ```
@@ -67,6 +67,7 @@ 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
70
71
  config.current_user_method = :authenticated_user
71
72
  config.must_have_roles = true
72
73
  config.when_actions_missing = -> (missing_actions, context) { ... }
@@ -74,6 +75,9 @@ Rabarber.configure do |config|
74
75
  config.when_unauthorized = -> (controller) { ... }
75
76
  end
76
77
  ```
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.
80
+
77
81
  - `current_user_method` must be a symbol representing the method that returns the currently authenticated user. The default value is `:current_user`.
78
82
 
79
83
  - `must_have_roles` must be a boolean determining whether a user with no roles can access endpoints permitted to everyone. The default value is `false` (allowing users without roles to access endpoints permitted to everyone).
@@ -109,13 +113,6 @@ By default, `#assign_roles` method will automatically create any roles that don'
109
113
  user.assign_roles(:accountant, :marketer, create_new: false)
110
114
  ```
111
115
 
112
- You can also explicitly create new roles simply by using:
113
-
114
- ```rb
115
- Rabarber::Role.create(name: "manager")
116
- ```
117
- The role names must be unique.
118
-
119
116
  **`#revoke_roles`**
120
117
 
121
118
  To revoke roles, use:
@@ -149,8 +146,6 @@ If you need to list all the role names available in your application, use:
149
146
  Rabarber::Role.names
150
147
  ```
151
148
 
152
- `Rabarber::Role` is a model that represents roles within your application. It has a single attribute, `name`, which is validated for both uniqueness and presence. You can treat `Rabarber::Role` as a regular Rails model and use Active Record methods on it if necessary.
153
-
154
149
  ## Authorization Rules
155
150
 
156
151
  Include `Rabarber::Authorization` module into the controller that needs authorization rules to be applied (authorization rules will be applied to the controller and its children). Typically, it is `ApplicationController`, but it can be any controller.
@@ -221,9 +216,9 @@ class InvoicesController < ApplicationController
221
216
  end
222
217
  ```
223
218
 
224
- This allows everyone to access `OrdersController` and its children and `index` action in `InvoicesController`.
219
+ 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`.
225
220
 
226
- 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 see anything.
221
+ 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.
227
222
 
228
223
  For more complex cases, Rabarber provides dynamic rules:
229
224
 
@@ -274,7 +269,16 @@ This means that `Crm::InvoicesController` is still accessible to `admin` but is
274
269
 
275
270
  ## View Helpers
276
271
 
277
- Rabarber also provides a couple of helpers that can be used in views: `visible_to` and `hidden_from`. The usage is straightforward:
272
+ Rabarber also provides a couple of helpers that can be used in views: `visible_to` and `hidden_from`. To use them, simply include `Rabarber::Helpers` in the desired helper (usually `ApplicationHelper`, but it can be any helper):
273
+
274
+ ```rb
275
+ module ApplicationHelper
276
+ include Rabarber::Helpers
277
+ ...
278
+ end
279
+ ```
280
+
281
+ The usage is straightforward:
278
282
 
279
283
  ```erb
280
284
  <%= visible_to(:admin, :manager) do %>
@@ -292,9 +296,17 @@ Rabarber also provides a couple of helpers that can be used in views: `visible_t
292
296
 
293
297
  Encountered a bug or facing a problem?
294
298
 
295
- - **Create an Issue**: If you've identified a problem or have a feature request, please create an issue on the gem's GitHub repository. Be sure to provide detailed information about the problem, including the steps to reproduce it.
296
- - **Contribute a Solution**: Found a fix for the issue or want to contribute to the project? Feel free to create a pull request with your changes.
299
+ - **Create an Issue**: If you've identified a problem, please create an issue on the gem's GitHub repository. Be sure to provide detailed information about the problem, including the steps to reproduce it.
300
+ - **Contribute a Solution**: Found a fix for the issue? Feel free to create a pull request with your changes.
301
+
302
+ ## Contributing
303
+
304
+ If you want to contribute, please read the [contributing guidelines](https://github.com/enjaku4/rabarber/blob/main/CONTRIBUTING.md).
297
305
 
298
306
  ## License
299
307
 
300
308
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
309
+
310
+ ## Code of Conduct
311
+
312
+ Everyone interacting in the Rabarber project is expected to follow the [code of conduct](https://github.com/enjaku4/rabarber/blob/main/CODE_OF_CONDUCT.md).
@@ -8,6 +8,8 @@ module Rabarber
8
8
 
9
9
  source_root File.expand_path("templates", __dir__)
10
10
 
11
+ argument :table_name, type: :string, required: true
12
+
11
13
  def create_migrations
12
14
  migration_template "create_rabarber_roles.rb.erb", "db/migrate/create_rabarber_roles.rb"
13
15
  end
@@ -9,7 +9,7 @@ class CreateRabarberRoles < ActiveRecord::Migration[<%= ActiveRecord::Migration.
9
9
 
10
10
  create_table :rabarber_roles_roleables, id: false do |t|
11
11
  t.belongs_to :role, null: false, index: true, foreign_key: { to_table: :rabarber_roles }
12
- t.belongs_to :roleable, null: false, index: true, foreign_key: { to_table: raise(Rabarber::Error, "Please specify your user model's table name") }
12
+ t.belongs_to :roleable, null: false, index: true, foreign_key: { to_table: <%= table_name.to_sym.inspect %> }
13
13
  end
14
14
 
15
15
  add_index :rabarber_roles_roleables, [:role_id, :roleable_id], unique: true
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rabarber
4
+ module Cache
5
+ module_function
6
+
7
+ ALL_ROLES_KEY = "rabarber:roles"
8
+
9
+ def fetch(key, options, &block)
10
+ enabled? ? Rails.cache.fetch(key, options, &block) : yield
11
+ end
12
+
13
+ def delete(key)
14
+ Rails.cache.delete(key) if enabled?
15
+ end
16
+
17
+ def enabled?
18
+ Rabarber::Configuration.instance.cache_enabled
19
+ end
20
+
21
+ def key_for(record)
22
+ "rabarber:roles_#{record.public_send(record.class.primary_key)}"
23
+ end
24
+
25
+ def clear
26
+ Rails.cache.delete_matched(/^rabarber/)
27
+ end
28
+ end
29
+ end
@@ -6,9 +6,11 @@ module Rabarber
6
6
  class Configuration
7
7
  include Singleton
8
8
 
9
- attr_reader :current_user_method, :must_have_roles, :when_actions_missing, :when_roles_missing, :when_unauthorized
9
+ attr_reader :cache_enabled, :current_user_method, :must_have_roles,
10
+ :when_actions_missing, :when_roles_missing, :when_unauthorized
10
11
 
11
12
  def initialize
13
+ @cache_enabled = default_cache_enabled
12
14
  @current_user_method = default_current_user_method
13
15
  @must_have_roles = default_must_have_roles
14
16
  @when_actions_missing = default_when_actions_missing
@@ -16,6 +18,12 @@ module Rabarber
16
18
  @when_unauthorized = default_when_unauthorized
17
19
  end
18
20
 
21
+ def cache_enabled=(value)
22
+ @cache_enabled = Rabarber::Input::Types::Booleans.new(
23
+ value, Rabarber::ConfigurationError, "Configuration 'cache_enabled' must be a Boolean"
24
+ ).process
25
+ end
26
+
19
27
  def current_user_method=(method_name)
20
28
  @current_user_method = Rabarber::Input::Types::Symbols.new(
21
29
  method_name, Rabarber::ConfigurationError, "Configuration 'current_user_method' must be a Symbol or a String"
@@ -48,6 +56,10 @@ module Rabarber
48
56
 
49
57
  private
50
58
 
59
+ def default_cache_enabled
60
+ true
61
+ end
62
+
51
63
  def default_current_user_method
52
64
  :current_user
53
65
  end
@@ -72,6 +84,7 @@ module Rabarber
72
84
 
73
85
  def default_when_unauthorized
74
86
  -> (controller) do
87
+ Rails.logger.tagged("Rabarber") { Rails.logger.warn "Unauthorized attempt" }
75
88
  if controller.request.format.html?
76
89
  controller.redirect_back fallback_location: controller.main_app.root_path
77
90
  else
@@ -28,11 +28,14 @@ module Rabarber
28
28
  Rabarber::Missing::Actions.new(self.class).handle
29
29
  Rabarber::Missing::Roles.new(self.class).handle
30
30
 
31
- return if Rabarber::Permissions.access_granted?(
32
- send(Rabarber::Configuration.instance.current_user_method).roles, self.class, action_name.to_sym, self
33
- )
31
+ return if Rabarber::Permissions.access_granted?(rabarber_roles, self.class, action_name.to_sym, self)
34
32
 
35
33
  Rabarber::Configuration.instance.when_unauthorized.call(self)
36
34
  end
35
+
36
+ def rabarber_roles
37
+ user = send(Rabarber::Configuration.instance.current_user_method)
38
+ user ? user.roles : []
39
+ end
37
40
  end
38
41
  end
@@ -16,9 +16,8 @@ module Rabarber
16
16
  return if missing_list.empty?
17
17
 
18
18
  missing_list.each do |item|
19
- Rabarber::Configuration.instance.public_send(configuration_name).call(
20
- item.missing, controller: item.controller, action: item.action
21
- )
19
+ context = item.action ? { controller: item.controller, action: item.action } : { controller: item.controller }
20
+ Rabarber::Configuration.instance.public_send(configuration_name).call(item.missing, context)
22
21
  end
23
22
  end
24
23
 
@@ -28,7 +28,9 @@ module Rabarber
28
28
  end
29
29
 
30
30
  def all_roles
31
- @all_roles ||= Rabarber::Role.names
31
+ @all_roles ||= Rabarber::Cache.fetch(
32
+ Rabarber::Cache::ALL_ROLES_KEY, expires_in: 1.day, race_condition_ttl: 10.seconds
33
+ ) { Rabarber::Role.names }
32
34
  end
33
35
  end
34
36
  end
@@ -17,7 +17,9 @@ module Rabarber
17
17
  end
18
18
 
19
19
  def roles
20
- rabarber_roles.names
20
+ Rabarber::Cache.fetch(Rabarber::Cache.key_for(self), expires_in: 1.hour, race_condition_ttl: 5.seconds) do
21
+ rabarber_roles.names
22
+ end
21
23
  end
22
24
 
23
25
  def has_role?(*role_names)
@@ -30,10 +32,14 @@ module Rabarber
30
32
  create_new_roles(roles_to_assign) if create_new
31
33
 
32
34
  rabarber_roles << Rabarber::Role.where(name: roles_to_assign) - rabarber_roles
35
+
36
+ delete_cache
33
37
  end
34
38
 
35
39
  def revoke_roles(*role_names)
36
40
  self.rabarber_roles = rabarber_roles - Rabarber::Role.where(name: process_role_names(role_names))
41
+
42
+ delete_cache
37
43
  end
38
44
 
39
45
  private
@@ -46,5 +52,9 @@ module Rabarber
46
52
  def process_role_names(role_names)
47
53
  Rabarber::Input::Roles.new(role_names).process
48
54
  end
55
+
56
+ def delete_cache
57
+ Rabarber::Cache.delete(Rabarber::Cache.key_for(self))
58
+ end
49
59
  end
50
60
  end
@@ -6,8 +6,16 @@ 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
10
+
9
11
  def self.names
10
12
  pluck(:name).map(&:to_sym)
11
13
  end
14
+
15
+ private
16
+
17
+ def delete_cache
18
+ Rabarber::Cache.delete(Rabarber::Cache::ALL_ROLES_KEY)
19
+ end
12
20
  end
13
21
  end
@@ -7,7 +7,7 @@ module Rabarber
7
7
  initializer "rabarber.after_initialize" do |app|
8
8
  app.config.after_initialize do
9
9
  Rabarber::Missing::Actions.new.handle
10
- Rabarber::Missing::Roles.new.handle
10
+ Rabarber::Missing::Roles.new.handle if Rabarber::Role.table_exists?
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rabarber
4
- VERSION = "1.2.0"
4
+ VERSION = "1.2.1"
5
5
  end
data/lib/rabarber.rb CHANGED
@@ -18,6 +18,8 @@ require_relative "rabarber/missing/base"
18
18
  require_relative "rabarber/missing/actions"
19
19
  require_relative "rabarber/missing/roles"
20
20
 
21
+ require_relative "rabarber/cache"
22
+
21
23
  require_relative "rabarber/controllers/concerns/authorization"
22
24
  require_relative "rabarber/helpers/helpers"
23
25
  require_relative "rabarber/models/concerns/has_roles"
data/rabarber.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "rabarber"
7
7
  spec.version = Rabarber::VERSION
8
8
  spec.authors = ["enjaku4", "trafium"]
9
- spec.email = ["enjaku4@gmail.com"]
9
+ spec.email = ["rabarber_gem@icloud.com"]
10
10
 
11
11
  spec.summary = "Simple authorization library for Ruby on Rails."
12
12
  spec.homepage = "https://github.com/enjaku4/rabarber"
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.0
4
+ version: 1.2.1
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-01-28 00:00:00.000000000 Z
12
+ date: 2024-02-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -27,7 +27,7 @@ dependencies:
27
27
  version: '6.1'
28
28
  description:
29
29
  email:
30
- - enjaku4@gmail.com
30
+ - rabarber_gem@icloud.com
31
31
  executables: []
32
32
  extensions: []
33
33
  extra_rdoc_files: []
@@ -39,6 +39,7 @@ files:
39
39
  - lib/generators/rabarber/templates/create_rabarber_roles.rb.erb
40
40
  - lib/rabarber.rb
41
41
  - lib/rabarber/access.rb
42
+ - lib/rabarber/cache.rb
42
43
  - lib/rabarber/configuration.rb
43
44
  - lib/rabarber/controllers/concerns/authorization.rb
44
45
  - lib/rabarber/helpers/helpers.rb