simple_authorize 0.1.0 โ 1.0.1
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.
- checksums.yaml +4 -4
- data/.overcommit.yml +55 -0
- data/.simplecov +15 -0
- data/CHANGELOG.md +113 -25
- data/CODE_OF_CONDUCT.md +129 -0
- data/CONTRIBUTING.md +182 -0
- data/LICENSE.txt +1 -1
- data/README.md +210 -15
- data/SECURITY.md +77 -0
- data/lib/generators/simple_authorize/install/install_generator.rb +1 -0
- data/lib/generators/simple_authorize/install/templates/simple_authorize.rb +8 -0
- data/lib/generators/simple_authorize/policy/policy_generator.rb +55 -0
- data/lib/generators/simple_authorize/policy/templates/policy.rb.tt +39 -0
- data/lib/generators/simple_authorize/policy/templates/policy_spec.rb.tt +73 -0
- data/lib/generators/simple_authorize/policy/templates/policy_test.rb.tt +65 -0
- data/lib/simple_authorize/configuration.rb +21 -0
- data/lib/simple_authorize/controller.rb +336 -38
- data/lib/simple_authorize/policy.rb +22 -0
- data/lib/simple_authorize/railtie.rb +20 -0
- data/lib/simple_authorize/rspec.rb +149 -0
- data/lib/simple_authorize/test_helpers.rb +115 -0
- data/lib/simple_authorize/version.rb +1 -1
- data/lib/simple_authorize.rb +6 -17
- data/spec/examples.txt +51 -0
- data/spec/rspec_matchers_spec.rb +235 -0
- data/spec/spec_helper.rb +116 -0
- metadata +53 -5
data/README.md
CHANGED
|
@@ -1,32 +1,44 @@
|
|
|
1
1
|
# SimpleAuthorize
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/simple_authorize)
|
|
4
4
|
[](https://github.com/scottlaplant/simple_authorize/actions)
|
|
5
|
+
[](https://rubygems.org/gems/simple_authorize)
|
|
5
6
|
|
|
6
7
|
SimpleAuthorize is a lightweight, powerful authorization framework for Rails that provides policy-based access control without external dependencies. Inspired by Pundit, it offers a clean API for managing permissions in your Rails applications.
|
|
7
8
|
|
|
8
9
|
## Features
|
|
9
10
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
11
|
+
- **Policy-Based Authorization** - Define authorization rules in dedicated policy classes
|
|
12
|
+
- **Scope Filtering** - Automatically filter collections based on user permissions
|
|
13
|
+
- **Role-Based Access** - Built-in support for role-based authorization
|
|
14
|
+
- **Zero Dependencies** - No external gems required (only Rails)
|
|
15
|
+
- **Strong Parameters Integration** - Automatically build permitted params from policies
|
|
16
|
+
- **Test Friendly** - Easy to test policies in isolation
|
|
17
|
+
- **Rails Generators** - Quickly scaffold policies for your models
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
Install the gem directly:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gem install simple_authorize
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or add this line to your application's Gemfile:
|
|
21
28
|
|
|
22
29
|
```ruby
|
|
23
30
|
gem 'simple_authorize'
|
|
24
31
|
```
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
Then execute:
|
|
27
34
|
|
|
28
35
|
```bash
|
|
29
36
|
bundle install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
After installation, run the generator to set up your application:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
30
42
|
rails generate simple_authorize:install
|
|
31
43
|
```
|
|
32
44
|
|
|
@@ -47,7 +59,17 @@ end
|
|
|
47
59
|
|
|
48
60
|
### 2. Create a Policy
|
|
49
61
|
|
|
50
|
-
|
|
62
|
+
Generate a policy for your model using the generator:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
rails generate simple_authorize:policy Post
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This creates:
|
|
69
|
+
- `app/policies/post_policy.rb` - Policy class with CRUD methods
|
|
70
|
+
- `test/policies/post_policy_test.rb` - Test file (or spec file with `--spec`)
|
|
71
|
+
|
|
72
|
+
Or create a policy class manually in `app/policies/`:
|
|
51
73
|
|
|
52
74
|
```ruby
|
|
53
75
|
# app/policies/post_policy.rb
|
|
@@ -197,6 +219,181 @@ def post_params
|
|
|
197
219
|
end
|
|
198
220
|
```
|
|
199
221
|
|
|
222
|
+
## Generators
|
|
223
|
+
|
|
224
|
+
SimpleAuthorize provides Rails generators to quickly scaffold policies:
|
|
225
|
+
|
|
226
|
+
### Install Generator
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
rails generate simple_authorize:install
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Creates:
|
|
233
|
+
- `config/initializers/simple_authorize.rb` - Configuration file
|
|
234
|
+
- `app/policies/application_policy.rb` - Base policy class
|
|
235
|
+
|
|
236
|
+
### Policy Generator
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
rails generate simple_authorize:policy Post
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Creates:
|
|
243
|
+
- `app/policies/post_policy.rb` - Policy with CRUD methods and scope
|
|
244
|
+
- `test/policies/post_policy_test.rb` - Minitest tests
|
|
245
|
+
|
|
246
|
+
**Options:**
|
|
247
|
+
- `--spec` - Generate RSpec tests instead of Minitest
|
|
248
|
+
- `--skip-test` - Skip test file generation
|
|
249
|
+
|
|
250
|
+
**Examples:**
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Generate policy with RSpec tests
|
|
254
|
+
rails generate simple_authorize:policy Post --spec
|
|
255
|
+
|
|
256
|
+
# Generate policy without tests
|
|
257
|
+
rails generate simple_authorize:policy Post --skip-test
|
|
258
|
+
|
|
259
|
+
# Generate namespaced policy
|
|
260
|
+
rails generate simple_authorize:policy Admin::Post
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Configuration
|
|
264
|
+
|
|
265
|
+
SimpleAuthorize can be configured in `config/initializers/simple_authorize.rb`:
|
|
266
|
+
|
|
267
|
+
### Policy Caching
|
|
268
|
+
|
|
269
|
+
Enable policy caching to improve performance by caching policy instances per request:
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
SimpleAuthorize.configure do |config|
|
|
273
|
+
config.enable_policy_cache = true
|
|
274
|
+
end
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**How it works:**
|
|
278
|
+
- Policy instances are cached for the duration of a single request
|
|
279
|
+
- Cache is automatically scoped by user, record, and policy class
|
|
280
|
+
- Each unique combination gets its own cached instance
|
|
281
|
+
- Cache is automatically cleared between requests
|
|
282
|
+
- Particularly useful in views where the same policy may be checked multiple times
|
|
283
|
+
|
|
284
|
+
**Example performance impact:**
|
|
285
|
+
|
|
286
|
+
```erb
|
|
287
|
+
<!-- Without caching: Creates 3 separate PostPolicy instances -->
|
|
288
|
+
<% if policy(@post).update? %>
|
|
289
|
+
<%= link_to "Edit", edit_post_path(@post) %>
|
|
290
|
+
<% end %>
|
|
291
|
+
<% if policy(@post).destroy? %>
|
|
292
|
+
<%= link_to "Delete", post_path(@post) %>
|
|
293
|
+
<% end %>
|
|
294
|
+
<% if policy(@post).publish? %>
|
|
295
|
+
<%= link_to "Publish", publish_post_path(@post) %>
|
|
296
|
+
<% end %>
|
|
297
|
+
|
|
298
|
+
<!-- With caching: Reuses the same PostPolicy instance -->
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Testing:**
|
|
302
|
+
Use `clear_policy_cache` or `reset_authorization` to clear the cache in tests:
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
test "multiple checks use cached policy" do
|
|
306
|
+
SimpleAuthorize.configure { |config| config.enable_policy_cache = true }
|
|
307
|
+
|
|
308
|
+
policy1 = policy(@post)
|
|
309
|
+
policy2 = policy(@post)
|
|
310
|
+
assert_same policy1, policy2 # Same instance
|
|
311
|
+
|
|
312
|
+
clear_policy_cache
|
|
313
|
+
policy3 = policy(@post)
|
|
314
|
+
refute_same policy1, policy3 # New instance after clearing
|
|
315
|
+
end
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Instrumentation & Audit Logging
|
|
319
|
+
|
|
320
|
+
SimpleAuthorize emits `ActiveSupport::Notifications` events for all authorization checks, perfect for security auditing, debugging, and monitoring:
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
# Subscribe to authorization events
|
|
324
|
+
ActiveSupport::Notifications.subscribe("authorize.simple_authorize") do |name, start, finish, id, payload|
|
|
325
|
+
duration = finish - start
|
|
326
|
+
|
|
327
|
+
Rails.logger.info({
|
|
328
|
+
event: "authorization",
|
|
329
|
+
user_id: payload[:user_id],
|
|
330
|
+
action: payload[:query],
|
|
331
|
+
resource: "#{payload[:record_class]}##{payload[:record_id]}",
|
|
332
|
+
authorized: payload[:authorized],
|
|
333
|
+
duration_ms: (duration * 1000).round(2)
|
|
334
|
+
}.to_json)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Subscribe to policy scope events
|
|
338
|
+
ActiveSupport::Notifications.subscribe("policy_scope.simple_authorize") do |name, start, finish, id, payload|
|
|
339
|
+
Rails.logger.info("Policy scope applied for #{payload[:scope]} by user #{payload[:user_id]}")
|
|
340
|
+
end
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Event Payloads:**
|
|
344
|
+
|
|
345
|
+
Authorization events (`authorize.simple_authorize`):
|
|
346
|
+
- `user`: Current user object
|
|
347
|
+
- `user_id`: User ID
|
|
348
|
+
- `record`: The record being authorized
|
|
349
|
+
- `record_id`: Record ID
|
|
350
|
+
- `record_class`: Record class name
|
|
351
|
+
- `query`: Authorization method called (e.g., "update?")
|
|
352
|
+
- `policy_class`: Policy class used
|
|
353
|
+
- `authorized`: Boolean result
|
|
354
|
+
- `error`: Exception if authorization failed
|
|
355
|
+
- `controller`: Controller name (if available)
|
|
356
|
+
- `action`: Action name (if available)
|
|
357
|
+
|
|
358
|
+
Policy scope events (`policy_scope.simple_authorize`):
|
|
359
|
+
- `user`: Current user object
|
|
360
|
+
- `user_id`: User ID
|
|
361
|
+
- `scope`: The scope being filtered
|
|
362
|
+
- `policy_scope_class`: Scope class used
|
|
363
|
+
- `error`: Exception if scope failed
|
|
364
|
+
- `controller`: Controller name (if available)
|
|
365
|
+
- `action`: Action name (if available)
|
|
366
|
+
|
|
367
|
+
**Use Cases:**
|
|
368
|
+
- Security auditing and compliance
|
|
369
|
+
- Debugging authorization issues
|
|
370
|
+
- Monitoring authorization performance
|
|
371
|
+
- Sending failed authorization attempts to security services
|
|
372
|
+
- Tracking which users access sensitive resources
|
|
373
|
+
|
|
374
|
+
**Disable instrumentation** (if needed for performance in specific scenarios):
|
|
375
|
+
|
|
376
|
+
```ruby
|
|
377
|
+
SimpleAuthorize.configure do |config|
|
|
378
|
+
config.enable_instrumentation = false
|
|
379
|
+
end
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Other Configuration Options
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
SimpleAuthorize.configure do |config|
|
|
386
|
+
# Custom error message for unauthorized access
|
|
387
|
+
config.default_error_message = "Access denied!"
|
|
388
|
+
|
|
389
|
+
# Custom redirect path for unauthorized users
|
|
390
|
+
config.unauthorized_redirect_path = "/access-denied"
|
|
391
|
+
|
|
392
|
+
# Custom method to get current user (default: current_user)
|
|
393
|
+
config.current_user_method = :authenticated_user
|
|
394
|
+
end
|
|
395
|
+
```
|
|
396
|
+
|
|
200
397
|
## Advanced Features
|
|
201
398
|
|
|
202
399
|
### Headless Policies
|
|
@@ -284,7 +481,7 @@ end
|
|
|
284
481
|
|
|
285
482
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
286
483
|
|
|
287
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
484
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
288
485
|
|
|
289
486
|
## Comparison with Pundit
|
|
290
487
|
|
|
@@ -316,6 +513,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
316
513
|
|
|
317
514
|
## Credits
|
|
318
515
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
Inspired by [Pundit](https://github.com/varvet/pundit) by Elabs
|
|
516
|
+
SimpleAuthorize is heavily inspired by [Pundit](https://github.com/varvet/pundit) by Elabs. We're grateful to the Pundit team for pioneering this authorization pattern.
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
We release patches for security vulnerabilities. Currently supported versions:
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| ------- | ------------------ |
|
|
9
|
+
| 1.0.x | :white_check_mark: |
|
|
10
|
+
| < 1.0 | :x: |
|
|
11
|
+
|
|
12
|
+
## Reporting a Vulnerability
|
|
13
|
+
|
|
14
|
+
We take the security of SimpleAuthorize seriously. If you discover a security vulnerability, please report it privately.
|
|
15
|
+
|
|
16
|
+
### How to Report
|
|
17
|
+
|
|
18
|
+
**Please DO NOT open a public GitHub issue for security vulnerabilities.**
|
|
19
|
+
|
|
20
|
+
Please report security vulnerabilities to: **simpleauthorize@gmail.com**
|
|
21
|
+
|
|
22
|
+
Alternatively, you can use GitHub's private vulnerability reporting feature (see "Security" tab in the repository).
|
|
23
|
+
|
|
24
|
+
### What to Include
|
|
25
|
+
|
|
26
|
+
Please include the following information in your report:
|
|
27
|
+
|
|
28
|
+
- Type of vulnerability (e.g., authorization bypass, XSS, injection)
|
|
29
|
+
- Full paths of source files related to the vulnerability
|
|
30
|
+
- The location of the affected source code (tag/branch/commit or direct URL)
|
|
31
|
+
- Step-by-step instructions to reproduce the issue
|
|
32
|
+
- Proof-of-concept or exploit code (if possible)
|
|
33
|
+
- Impact of the issue, including how an attacker might exploit it
|
|
34
|
+
|
|
35
|
+
### Response Timeline
|
|
36
|
+
|
|
37
|
+
- **Initial Response**: Within 48 hours of receiving your report
|
|
38
|
+
- **Status Update**: Within 5 business days with an initial assessment
|
|
39
|
+
- **Fix Timeline**: Critical vulnerabilities will be addressed within 30 days
|
|
40
|
+
- **Disclosure**: We will coordinate with you on the disclosure timeline
|
|
41
|
+
|
|
42
|
+
### Security Update Process
|
|
43
|
+
|
|
44
|
+
1. We will confirm the vulnerability and determine its severity
|
|
45
|
+
2. We will develop and test a fix
|
|
46
|
+
3. We will release a new version with the security patch
|
|
47
|
+
4. We will publish a security advisory with details after the fix is released
|
|
48
|
+
5. We will credit you for the discovery (unless you prefer to remain anonymous)
|
|
49
|
+
|
|
50
|
+
## Security Best Practices
|
|
51
|
+
|
|
52
|
+
When using SimpleAuthorize in your application:
|
|
53
|
+
|
|
54
|
+
1. **Always verify authorization**: Use `verify_authorized` in controllers to ensure authorization is checked
|
|
55
|
+
2. **Secure policy defaults**: The default Policy class denies all actions - only permit what's necessary
|
|
56
|
+
3. **Test your policies**: Write comprehensive tests for all authorization logic
|
|
57
|
+
4. **Keep updated**: Regularly update to the latest version to get security patches
|
|
58
|
+
5. **Review policies**: Periodically audit your policy classes for authorization holes
|
|
59
|
+
|
|
60
|
+
## Known Security Considerations
|
|
61
|
+
|
|
62
|
+
### Authorization Bypass Prevention
|
|
63
|
+
|
|
64
|
+
- Always call `authorize` before performing sensitive actions
|
|
65
|
+
- Use `skip_authorization` explicitly and only when intentional
|
|
66
|
+
- Be careful with `headless_policy` - ensure proper authorization for non-resource actions
|
|
67
|
+
|
|
68
|
+
### Scope Security
|
|
69
|
+
|
|
70
|
+
- Always use `policy_scope` to filter collections
|
|
71
|
+
- Don't rely solely on view-level hiding - enforce at the data layer
|
|
72
|
+
- Test scope filtering with different user roles
|
|
73
|
+
|
|
74
|
+
## Dependencies
|
|
75
|
+
|
|
76
|
+
SimpleAuthorize has minimal dependencies (only Rails/ActiveSupport). We monitor our dependencies for security vulnerabilities and update promptly.
|
|
77
|
+
|
|
@@ -13,4 +13,12 @@ SimpleAuthorize.configure do |config|
|
|
|
13
13
|
|
|
14
14
|
# Custom redirect path for unauthorized access (default: uses referrer or root_path)
|
|
15
15
|
# config.unauthorized_redirect_path = "/unauthorized"
|
|
16
|
+
|
|
17
|
+
# Enable policy caching for performance optimization (default: false)
|
|
18
|
+
# When enabled, policy instances are cached per request, scoped by user, record, and policy class
|
|
19
|
+
# config.enable_policy_cache = true
|
|
20
|
+
|
|
21
|
+
# Enable instrumentation for authorization events (default: true)
|
|
22
|
+
# When enabled, emits ActiveSupport::Notifications events for all authorization checks
|
|
23
|
+
# config.enable_instrumentation = true
|
|
16
24
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module SimpleAuthorize
|
|
6
|
+
module Generators
|
|
7
|
+
# Rails generator to create a policy class for a model
|
|
8
|
+
class PolicyGenerator < Rails::Generators::NamedBase
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Creates a SimpleAuthorize policy class for a model"
|
|
12
|
+
|
|
13
|
+
argument :name, type: :string, required: true, banner: "ModelName"
|
|
14
|
+
|
|
15
|
+
class_option :spec, type: :boolean, default: false, desc: "Generate RSpec test file instead of Minitest"
|
|
16
|
+
class_option :skip_test, type: :boolean, default: false, desc: "Skip generating test file"
|
|
17
|
+
|
|
18
|
+
def create_policy_file
|
|
19
|
+
template "policy.rb.tt", File.join("app/policies", class_path, "#{file_name}_policy.rb")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_test_file
|
|
23
|
+
return if options[:skip_test]
|
|
24
|
+
|
|
25
|
+
if options[:spec]
|
|
26
|
+
template "policy_spec.rb.tt", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
|
|
27
|
+
else
|
|
28
|
+
template "policy_test.rb.tt", File.join("test/policies", class_path, "#{file_name}_policy_test.rb")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def policy_class_name
|
|
35
|
+
"#{class_name}Policy"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def model_class_name
|
|
39
|
+
class_name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def model_instance_name
|
|
43
|
+
file_name
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def namespaced_policy_class
|
|
47
|
+
if class_path.empty?
|
|
48
|
+
policy_class_name
|
|
49
|
+
else
|
|
50
|
+
"#{class_path.map(&:camelize).join("::")}::#{policy_class_name}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
<% if class_path.any? -%>
|
|
4
|
+
module <%= class_path.map(&:camelize).join('::') %>
|
|
5
|
+
<% end -%>
|
|
6
|
+
class <%= policy_class_name %> < ApplicationPolicy
|
|
7
|
+
def index?
|
|
8
|
+
false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show?
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create?
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def destroy?
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def permitted_attributes
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Scope < ApplicationPolicy::Scope
|
|
32
|
+
def resolve
|
|
33
|
+
scope.all
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
<% if class_path.any? -%>
|
|
38
|
+
end
|
|
39
|
+
<% end -%>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
<% if class_path.any? -%>
|
|
6
|
+
module <%= class_path.map(&:camelize).join('::') %>
|
|
7
|
+
<% end -%>
|
|
8
|
+
RSpec.describe <%= policy_class_name %>, type: :policy do
|
|
9
|
+
subject(:policy) { described_class.new(user, <%= model_instance_name %>) }
|
|
10
|
+
|
|
11
|
+
let(:user) { User.new }
|
|
12
|
+
let(:<%= model_instance_name %>) { <%= model_class_name %>.new }
|
|
13
|
+
|
|
14
|
+
describe "#index?" do
|
|
15
|
+
it "denies access by default" do
|
|
16
|
+
expect(policy).not_to permit_action(:index)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# TODO: Add your authorization logic tests
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#show?" do
|
|
23
|
+
it "denies access by default" do
|
|
24
|
+
expect(policy).not_to permit_action(:show)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# TODO: Add your authorization logic tests
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#create?" do
|
|
31
|
+
it "denies access by default" do
|
|
32
|
+
expect(policy).not_to permit_action(:create)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# TODO: Add your authorization logic tests
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "#update?" do
|
|
39
|
+
it "denies access by default" do
|
|
40
|
+
expect(policy).not_to permit_action(:update)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# TODO: Add your authorization logic tests
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#destroy?" do
|
|
47
|
+
it "denies access by default" do
|
|
48
|
+
expect(policy).not_to permit_action(:destroy)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# TODO: Add your authorization logic tests
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "#permitted_attributes" do
|
|
55
|
+
it "returns empty array by default" do
|
|
56
|
+
expect(policy.permitted_attributes).to eq([])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# TODO: Add your permitted attributes tests
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "Scope" do
|
|
63
|
+
subject(:scope) { <%= namespaced_policy_class %>::Scope.new(user, <%= model_class_name %>.all) }
|
|
64
|
+
|
|
65
|
+
it "returns all records by default" do
|
|
66
|
+
# TODO: Add your scope tests
|
|
67
|
+
expect(scope.resolve).to eq(<%= model_class_name %>.all)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
<% if class_path.any? -%>
|
|
72
|
+
end
|
|
73
|
+
<% end -%>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
<% if class_path.any? -%>
|
|
6
|
+
module <%= class_path.map(&:camelize).join('::') %>
|
|
7
|
+
<% end -%>
|
|
8
|
+
class <%= policy_class_name %>Test < ActiveSupport::TestCase
|
|
9
|
+
def setup
|
|
10
|
+
@user = User.new
|
|
11
|
+
@<%= model_instance_name %> = <%= model_class_name %>.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
test "index?" do
|
|
15
|
+
policy = <%= namespaced_policy_class %>.new(@user, @<%= model_instance_name %>)
|
|
16
|
+
|
|
17
|
+
# TODO: Add your authorization logic tests
|
|
18
|
+
refute policy.index?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
test "show?" do
|
|
22
|
+
policy = <%= namespaced_policy_class %>.new(@user, @<%= model_instance_name %>)
|
|
23
|
+
|
|
24
|
+
# TODO: Add your authorization logic tests
|
|
25
|
+
refute policy.show?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test "create?" do
|
|
29
|
+
policy = <%= namespaced_policy_class %>.new(@user, @<%= model_instance_name %>)
|
|
30
|
+
|
|
31
|
+
# TODO: Add your authorization logic tests
|
|
32
|
+
refute policy.create?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "update?" do
|
|
36
|
+
policy = <%= namespaced_policy_class %>.new(@user, @<%= model_instance_name %>)
|
|
37
|
+
|
|
38
|
+
# TODO: Add your authorization logic tests
|
|
39
|
+
refute policy.update?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test "destroy?" do
|
|
43
|
+
policy = <%= namespaced_policy_class %>.new(@user, @<%= model_instance_name %>)
|
|
44
|
+
|
|
45
|
+
# TODO: Add your authorization logic tests
|
|
46
|
+
refute policy.destroy?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test "scope" do
|
|
50
|
+
# TODO: Add your scope tests
|
|
51
|
+
# For example:
|
|
52
|
+
# scope = <%= namespaced_policy_class %>::Scope.new(@user, <%= model_class_name %>.all)
|
|
53
|
+
# assert_equal expected_records, scope.resolve
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
test "permitted_attributes" do
|
|
57
|
+
policy = <%= namespaced_policy_class %>.new(@user, @<%= model_instance_name %>)
|
|
58
|
+
|
|
59
|
+
# TODO: Add your permitted attributes tests
|
|
60
|
+
assert_equal [], policy.permitted_attributes
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
<% if class_path.any? -%>
|
|
64
|
+
end
|
|
65
|
+
<% end -%>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleAuthorize
|
|
4
|
+
# Configuration options for SimpleAuthorize
|
|
4
5
|
class Configuration
|
|
5
6
|
# Default error message shown to users when not authorized
|
|
6
7
|
attr_accessor :default_error_message
|
|
@@ -14,11 +15,31 @@ module SimpleAuthorize
|
|
|
14
15
|
# Custom redirect path for unauthorized access
|
|
15
16
|
attr_accessor :unauthorized_redirect_path
|
|
16
17
|
|
|
18
|
+
# Enable policy caching for performance optimization (opt-in)
|
|
19
|
+
attr_accessor :enable_policy_cache
|
|
20
|
+
|
|
21
|
+
# Enable instrumentation for authorization events (default: true)
|
|
22
|
+
attr_accessor :enable_instrumentation
|
|
23
|
+
|
|
24
|
+
# Include detailed error information in API responses (default: false)
|
|
25
|
+
attr_accessor :api_error_details
|
|
26
|
+
|
|
27
|
+
# Enable I18n support for error messages (default: false)
|
|
28
|
+
attr_accessor :i18n_enabled
|
|
29
|
+
|
|
30
|
+
# I18n scope for translations (default: 'simple_authorize')
|
|
31
|
+
attr_accessor :i18n_scope
|
|
32
|
+
|
|
17
33
|
def initialize
|
|
18
34
|
@default_error_message = "You are not authorized to perform this action."
|
|
19
35
|
@auto_verify = false
|
|
20
36
|
@current_user_method = :current_user
|
|
21
37
|
@unauthorized_redirect_path = nil
|
|
38
|
+
@enable_policy_cache = false
|
|
39
|
+
@enable_instrumentation = true
|
|
40
|
+
@api_error_details = false
|
|
41
|
+
@i18n_enabled = false
|
|
42
|
+
@i18n_scope = "simple_authorize"
|
|
22
43
|
end
|
|
23
44
|
end
|
|
24
45
|
|