simple_authorize 1.0.1 → 1.1.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/CHANGELOG.md +27 -0
- data/CLAUDE.md +1 -0
- data/README.md +213 -0
- data/lib/simple_authorize/controller.rb +23 -3
- data/lib/simple_authorize/policy.rb +12 -2
- data/lib/simple_authorize/policy_modules/approvable.rb +117 -0
- data/lib/simple_authorize/policy_modules/ownable.rb +65 -0
- data/lib/simple_authorize/policy_modules/publishable.rb +81 -0
- data/lib/simple_authorize/policy_modules/soft_deletable.rb +88 -0
- data/lib/simple_authorize/policy_modules/timestamped.rb +104 -0
- data/lib/simple_authorize/policy_modules.rb +27 -0
- data/lib/simple_authorize/version.rb +1 -1
- data/lib/simple_authorize.rb +1 -0
- data/spec/examples.txt +24 -24
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b16462d63472a968792e6272f876b82beba2fadb48a675a5cb2d8d03dedec42f
|
|
4
|
+
data.tar.gz: 42553420c691366876062a93597613386542b26f997b42424d4fb8233aa09802
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4013198e95c910dd1377835fab2701494666217ee87081621e6121e3a9750cf41cbad210583c6ff52ef95d5d7be7c9350e5966ccb350cc877da64b1f482ac2f8
|
|
7
|
+
data.tar.gz: 5fdc05879861c05aa16763b5fa7d3331e7fed2cf6d4e8443f7109fac0570f979dd584073774a557333157544f8ec223386fe6705ea64abc80f5283abb586cbd4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.1] - 2025-11-05
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- RuboCop compliance for all new policy modules
|
|
12
|
+
- Method naming convention (`standard_permissions` instead of `standard_permissions?`)
|
|
13
|
+
- Simplified conditional logic in Approvable module
|
|
14
|
+
- Code style improvements across test files
|
|
15
|
+
|
|
16
|
+
## [1.1.0] - 2025-11-05
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
#### Policy Composition
|
|
21
|
+
- **Reusable Policy Modules** - Built-in modules for common authorization patterns
|
|
22
|
+
- **Ownable Module** - Ownership-based authorization helpers
|
|
23
|
+
- **Publishable Module** - Publishing workflow authorization
|
|
24
|
+
- **Timestamped Module** - Time-based access control
|
|
25
|
+
- **Approvable Module** - Approval workflow helpers
|
|
26
|
+
- **SoftDeletable Module** - Soft deletion authorization
|
|
27
|
+
- **Custom Module Support** - Easy creation of custom authorization modules
|
|
28
|
+
|
|
29
|
+
#### Context-Aware Policies
|
|
30
|
+
- **Request Context** - Pass additional context to policies (IP, time, location, etc.)
|
|
31
|
+
- **Controller Integration** - `authorization_context` method for building context
|
|
32
|
+
- **Context in Scopes** - Context available in Policy::Scope classes
|
|
33
|
+
- **Common Patterns** - Built-in support for geographic restrictions, time-based access, rate limiting
|
|
34
|
+
|
|
8
35
|
## [1.0.0] - 2025-11-03
|
|
9
36
|
|
|
10
37
|
### Added
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- do not automatically commit or push to the repo, please
|
data/README.md
CHANGED
|
@@ -11,6 +11,8 @@ SimpleAuthorize is a lightweight, powerful authorization framework for Rails tha
|
|
|
11
11
|
- **Policy-Based Authorization** - Define authorization rules in dedicated policy classes
|
|
12
12
|
- **Scope Filtering** - Automatically filter collections based on user permissions
|
|
13
13
|
- **Role-Based Access** - Built-in support for role-based authorization
|
|
14
|
+
- **Policy Composition** - Mix and match reusable authorization modules
|
|
15
|
+
- **Context-Aware Policies** - Make authorization decisions based on request context (IP, time, location, etc.)
|
|
14
16
|
- **Zero Dependencies** - No external gems required (only Rails)
|
|
15
17
|
- **Strong Parameters Integration** - Automatically build permitted params from policies
|
|
16
18
|
- **Test Friendly** - Easy to test policies in isolation
|
|
@@ -394,6 +396,217 @@ SimpleAuthorize.configure do |config|
|
|
|
394
396
|
end
|
|
395
397
|
```
|
|
396
398
|
|
|
399
|
+
## Policy Composition
|
|
400
|
+
|
|
401
|
+
Policy Composition allows you to build complex authorization policies by combining reusable modules. This promotes DRY code and consistent authorization patterns across your application.
|
|
402
|
+
|
|
403
|
+
### Using Built-in Policy Modules
|
|
404
|
+
|
|
405
|
+
SimpleAuthorize provides several ready-to-use policy modules:
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
class ArticlePolicy < ApplicationPolicy
|
|
409
|
+
include SimpleAuthorize::PolicyModules::Ownable
|
|
410
|
+
include SimpleAuthorize::PolicyModules::Publishable
|
|
411
|
+
|
|
412
|
+
def show?
|
|
413
|
+
published? || owner_or_admin?
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def update?
|
|
417
|
+
owner_or_admin? && not_published?
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Available Policy Modules
|
|
423
|
+
|
|
424
|
+
#### Ownable
|
|
425
|
+
Provides ownership-based authorization:
|
|
426
|
+
- `owner?` - Check if user owns the record
|
|
427
|
+
- `owner_or_admin?` - Check if user is owner or admin
|
|
428
|
+
- `can_modify?` - Common pattern for modification rights
|
|
429
|
+
|
|
430
|
+
#### Publishable
|
|
431
|
+
For content with draft/published states:
|
|
432
|
+
- `published?` - Check if record is published
|
|
433
|
+
- `can_publish?` - Check if user can publish
|
|
434
|
+
- `can_preview?` - Check if user can preview drafts
|
|
435
|
+
|
|
436
|
+
#### Timestamped
|
|
437
|
+
Time-based authorization:
|
|
438
|
+
- `expired?` - Check if record has expired
|
|
439
|
+
- `within_time_window?` - Check if record is in valid time range
|
|
440
|
+
- `locked?` - Check if record is time-locked
|
|
441
|
+
|
|
442
|
+
#### Approvable
|
|
443
|
+
For approval workflows:
|
|
444
|
+
- `approved?` - Check if record is approved
|
|
445
|
+
- `can_approve?` - Check if user can approve (not their own content)
|
|
446
|
+
- `can_submit_for_approval?` - Check if user can submit for approval
|
|
447
|
+
|
|
448
|
+
#### SoftDeletable
|
|
449
|
+
For soft deletion support:
|
|
450
|
+
- `soft_deleted?` - Check if record is soft deleted
|
|
451
|
+
- `can_restore?` - Check if user can restore
|
|
452
|
+
- `can_permanently_destroy?` - Check if user can hard delete
|
|
453
|
+
|
|
454
|
+
### Creating Custom Policy Modules
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
module MyApp::PolicyModules::Subscribable
|
|
458
|
+
protected
|
|
459
|
+
|
|
460
|
+
def subscriber?
|
|
461
|
+
user&.subscriptions&.active&.any?
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def premium_subscriber?
|
|
465
|
+
user&.subscription&.premium?
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def can_access_premium_content?
|
|
469
|
+
premium_subscriber? || admin?
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
class PremiumContentPolicy < ApplicationPolicy
|
|
474
|
+
include MyApp::PolicyModules::Subscribable
|
|
475
|
+
|
|
476
|
+
def show?
|
|
477
|
+
can_access_premium_content?
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Context-Aware Policies
|
|
483
|
+
|
|
484
|
+
Context-Aware Policies allow you to make authorization decisions based on additional context beyond just the user and record. This is useful for IP-based restrictions, time-based access, rate limiting, and more.
|
|
485
|
+
|
|
486
|
+
### Basic Usage
|
|
487
|
+
|
|
488
|
+
Override the `authorization_context` method in your controller:
|
|
489
|
+
|
|
490
|
+
```ruby
|
|
491
|
+
class ApplicationController < ActionController::Base
|
|
492
|
+
include SimpleAuthorize::Controller
|
|
493
|
+
|
|
494
|
+
private
|
|
495
|
+
|
|
496
|
+
def authorization_context
|
|
497
|
+
{
|
|
498
|
+
ip_address: request.remote_ip,
|
|
499
|
+
user_agent: request.user_agent,
|
|
500
|
+
current_time: Time.current,
|
|
501
|
+
country: request.location&.country,
|
|
502
|
+
two_factor_verified: session[:two_factor_verified],
|
|
503
|
+
user_plan: current_user&.subscription&.plan
|
|
504
|
+
}
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Using Context in Policies
|
|
510
|
+
|
|
511
|
+
Access context in your policies through the `context` method:
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
class SecureDocumentPolicy < ApplicationPolicy
|
|
515
|
+
def show?
|
|
516
|
+
# Require 2FA for sensitive documents
|
|
517
|
+
return false unless context[:two_factor_verified]
|
|
518
|
+
|
|
519
|
+
# Check IP restrictions
|
|
520
|
+
return false unless trusted_ip?
|
|
521
|
+
|
|
522
|
+
owner_or_admin?
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
private
|
|
526
|
+
|
|
527
|
+
def trusted_ip?
|
|
528
|
+
return true if context[:ip_address].nil?
|
|
529
|
+
|
|
530
|
+
trusted_ips = ["192.168.1.0/24", "10.0.0.0/8"]
|
|
531
|
+
trusted_ips.any? { |range| IPAddr.new(range).include?(context[:ip_address]) }
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Common Context Patterns
|
|
537
|
+
|
|
538
|
+
#### Geographic Restrictions
|
|
539
|
+
```ruby
|
|
540
|
+
class RegionalContentPolicy < ApplicationPolicy
|
|
541
|
+
def show?
|
|
542
|
+
allowed_countries = ["US", "CA", "UK"]
|
|
543
|
+
allowed_countries.include?(context[:country]) || admin?
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### Time-Based Access
|
|
549
|
+
```ruby
|
|
550
|
+
class BusinessHoursPolicy < ApplicationPolicy
|
|
551
|
+
def create?
|
|
552
|
+
return true if admin?
|
|
553
|
+
|
|
554
|
+
hour = context[:current_time].hour
|
|
555
|
+
hour >= 9 && hour < 17 # 9 AM to 5 PM only
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Rate Limiting
|
|
561
|
+
```ruby
|
|
562
|
+
class ApiPolicy < ApplicationPolicy
|
|
563
|
+
def create?
|
|
564
|
+
return true if admin?
|
|
565
|
+
|
|
566
|
+
request_count = context[:request_count] || 0
|
|
567
|
+
request_count < 100 # Limit to 100 requests
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### Plan-Based Features
|
|
573
|
+
```ruby
|
|
574
|
+
class ExportPolicy < ApplicationPolicy
|
|
575
|
+
def export?
|
|
576
|
+
case context[:user_plan]
|
|
577
|
+
when "enterprise"
|
|
578
|
+
true
|
|
579
|
+
when "pro"
|
|
580
|
+
owner_or_admin?
|
|
581
|
+
when "basic"
|
|
582
|
+
admin?
|
|
583
|
+
else
|
|
584
|
+
false
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Context with Policy Scopes
|
|
591
|
+
|
|
592
|
+
Context is also available in policy scopes:
|
|
593
|
+
|
|
594
|
+
```ruby
|
|
595
|
+
class DocumentPolicy < ApplicationPolicy
|
|
596
|
+
class Scope < ApplicationPolicy::Scope
|
|
597
|
+
def resolve
|
|
598
|
+
if context[:department]
|
|
599
|
+
scope.where(department: context[:department])
|
|
600
|
+
elsif user.admin?
|
|
601
|
+
scope.all
|
|
602
|
+
else
|
|
603
|
+
scope.where(user: user)
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
```
|
|
609
|
+
|
|
397
610
|
## Advanced Features
|
|
398
611
|
|
|
399
612
|
### Headless Policies
|
|
@@ -141,9 +141,9 @@ module SimpleAuthorize
|
|
|
141
141
|
if SimpleAuthorize.configuration.enable_policy_cache
|
|
142
142
|
policy_cache_key = build_policy_cache_key(record, policy_class)
|
|
143
143
|
@_policy_cache ||= {}
|
|
144
|
-
@_policy_cache[policy_cache_key] ||= policy_class.new(authorized_user, record)
|
|
144
|
+
@_policy_cache[policy_cache_key] ||= policy_class.new(authorized_user, record, context: authorization_context)
|
|
145
145
|
else
|
|
146
|
-
policy_class.new(authorized_user, record)
|
|
146
|
+
policy_class.new(authorized_user, record, context: authorization_context)
|
|
147
147
|
end
|
|
148
148
|
rescue NameError
|
|
149
149
|
raise PolicyNotDefinedError, "unable to find policy `#{policy_class}` for `#{record}`"
|
|
@@ -163,7 +163,7 @@ module SimpleAuthorize
|
|
|
163
163
|
error = nil
|
|
164
164
|
|
|
165
165
|
begin
|
|
166
|
-
result = policy_scope_class.new(authorized_user, scope).resolve
|
|
166
|
+
result = policy_scope_class.new(authorized_user, scope, context: authorization_context).resolve
|
|
167
167
|
rescue NameError
|
|
168
168
|
error = PolicyNotDefinedError.new("unable to find scope `#{policy_scope_class}` for `#{scope}`")
|
|
169
169
|
end
|
|
@@ -277,6 +277,26 @@ module SimpleAuthorize
|
|
|
277
277
|
current_user
|
|
278
278
|
end
|
|
279
279
|
|
|
280
|
+
# Build context for authorization (can be overridden)
|
|
281
|
+
# Override this method in your ApplicationController to provide
|
|
282
|
+
# context data for policies
|
|
283
|
+
#
|
|
284
|
+
# Example:
|
|
285
|
+
# def authorization_context
|
|
286
|
+
# {
|
|
287
|
+
# ip_address: request.remote_ip,
|
|
288
|
+
# user_agent: request.user_agent,
|
|
289
|
+
# subdomain: request.subdomain,
|
|
290
|
+
# current_time: Time.current,
|
|
291
|
+
# request_count: rate_limiter.count_for(current_user),
|
|
292
|
+
# two_factor_verified: session[:two_factor_verified],
|
|
293
|
+
# user_plan: current_user&.subscription&.plan
|
|
294
|
+
# }
|
|
295
|
+
# end
|
|
296
|
+
def authorization_context
|
|
297
|
+
{}
|
|
298
|
+
end
|
|
299
|
+
|
|
280
300
|
# Clear the policy cache
|
|
281
301
|
def clear_policy_cache
|
|
282
302
|
@_policy_cache = nil
|
|
@@ -5,9 +5,10 @@ module SimpleAuthorize
|
|
|
5
5
|
class Policy
|
|
6
6
|
attr_reader :user, :record
|
|
7
7
|
|
|
8
|
-
def initialize(user, record)
|
|
8
|
+
def initialize(user, record, context: nil)
|
|
9
9
|
@user = user
|
|
10
10
|
@record = record
|
|
11
|
+
@context = context
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
# Default policies - deny everything by default
|
|
@@ -64,6 +65,10 @@ module SimpleAuthorize
|
|
|
64
65
|
# Helper methods
|
|
65
66
|
protected
|
|
66
67
|
|
|
68
|
+
def context
|
|
69
|
+
@context || {}
|
|
70
|
+
end
|
|
71
|
+
|
|
67
72
|
def admin?
|
|
68
73
|
user&.admin?
|
|
69
74
|
end
|
|
@@ -96,9 +101,10 @@ module SimpleAuthorize
|
|
|
96
101
|
class Scope
|
|
97
102
|
attr_reader :user, :scope
|
|
98
103
|
|
|
99
|
-
def initialize(user, scope)
|
|
104
|
+
def initialize(user, scope, context: nil)
|
|
100
105
|
@user = user
|
|
101
106
|
@scope = scope
|
|
107
|
+
@context = context
|
|
102
108
|
end
|
|
103
109
|
|
|
104
110
|
def resolve
|
|
@@ -107,6 +113,10 @@ module SimpleAuthorize
|
|
|
107
113
|
|
|
108
114
|
protected
|
|
109
115
|
|
|
116
|
+
def context
|
|
117
|
+
@context || {}
|
|
118
|
+
end
|
|
119
|
+
|
|
110
120
|
def admin?
|
|
111
121
|
user&.admin?
|
|
112
122
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAuthorize
|
|
4
|
+
module PolicyModules
|
|
5
|
+
# Provides approval workflow authorization methods
|
|
6
|
+
#
|
|
7
|
+
# Include this module for content that requires approval:
|
|
8
|
+
#
|
|
9
|
+
# class DocumentPolicy < SimpleAuthorize::Policy
|
|
10
|
+
# include SimpleAuthorize::PolicyModules::Approvable
|
|
11
|
+
#
|
|
12
|
+
# def update?
|
|
13
|
+
# not_approved? && (owner? || admin?)
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Assumes records have approval-related fields like approved, approved_at,
|
|
18
|
+
# approval_status, pending_approval, etc.
|
|
19
|
+
module Approvable
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
# Check if the record is approved
|
|
23
|
+
def approved?
|
|
24
|
+
return false unless record
|
|
25
|
+
|
|
26
|
+
if record.respond_to?(:approved?)
|
|
27
|
+
record.approved?
|
|
28
|
+
elsif record.respond_to?(:approved)
|
|
29
|
+
record.approved == true
|
|
30
|
+
elsif record.respond_to?(:approval_status)
|
|
31
|
+
record.approval_status == "approved"
|
|
32
|
+
elsif record.respond_to?(:approved_at)
|
|
33
|
+
record.approved_at.present?
|
|
34
|
+
else
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if the record is pending approval
|
|
40
|
+
def pending_approval?
|
|
41
|
+
return false unless record
|
|
42
|
+
|
|
43
|
+
if record.respond_to?(:pending_approval?)
|
|
44
|
+
record.pending_approval?
|
|
45
|
+
elsif record.respond_to?(:pending_approval)
|
|
46
|
+
record.pending_approval == true
|
|
47
|
+
elsif record.respond_to?(:approval_status)
|
|
48
|
+
record.approval_status == "pending"
|
|
49
|
+
elsif record.respond_to?(:submitted_for_approval_at)
|
|
50
|
+
record.submitted_for_approval_at.present? && !approved? && !rejected?
|
|
51
|
+
else
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if the record is rejected
|
|
57
|
+
def rejected?
|
|
58
|
+
return false unless record
|
|
59
|
+
|
|
60
|
+
if record.respond_to?(:rejected?)
|
|
61
|
+
record.rejected?
|
|
62
|
+
elsif record.respond_to?(:rejected)
|
|
63
|
+
record.rejected == true
|
|
64
|
+
elsif record.respond_to?(:approval_status)
|
|
65
|
+
record.approval_status == "rejected"
|
|
66
|
+
elsif record.respond_to?(:rejected_at)
|
|
67
|
+
record.rejected_at.present?
|
|
68
|
+
else
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check if the record is not approved
|
|
74
|
+
def not_approved?
|
|
75
|
+
!approved?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if user can approve (cannot approve own content)
|
|
79
|
+
def can_approve?
|
|
80
|
+
return false unless logged_in?
|
|
81
|
+
|
|
82
|
+
if admin?
|
|
83
|
+
true
|
|
84
|
+
elsif contributor?
|
|
85
|
+
# Contributors can approve others' content but not their own
|
|
86
|
+
!owner?
|
|
87
|
+
else
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Check if user can reject
|
|
93
|
+
def can_reject?
|
|
94
|
+
can_approve?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Check if user can submit for approval
|
|
98
|
+
def can_submit_for_approval?
|
|
99
|
+
owner? && not_approved? && !pending_approval?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Check if user can withdraw from approval
|
|
103
|
+
def can_withdraw_approval?
|
|
104
|
+
owner? && pending_approval?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Check if content can be edited (typically not after approval)
|
|
108
|
+
def can_edit_with_approval?
|
|
109
|
+
if approved?
|
|
110
|
+
admin? # Only admins can edit approved content
|
|
111
|
+
else
|
|
112
|
+
owner? || admin? # Owner can edit rejected or unapproved content
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAuthorize
|
|
4
|
+
module PolicyModules
|
|
5
|
+
# Provides ownership-based authorization methods
|
|
6
|
+
#
|
|
7
|
+
# Include this module in your policy to add owner-based permissions:
|
|
8
|
+
#
|
|
9
|
+
# class PostPolicy < SimpleAuthorize::Policy
|
|
10
|
+
# include SimpleAuthorize::PolicyModules::Ownable
|
|
11
|
+
#
|
|
12
|
+
# def show?
|
|
13
|
+
# published? || owner_or_admin?
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# This module assumes your record has a `user_id` field that matches
|
|
18
|
+
# the user's ID. Override the `owner?` method if you need different logic.
|
|
19
|
+
module Ownable
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
# Check if the current user owns the record
|
|
23
|
+
def owner?
|
|
24
|
+
return false unless user && record
|
|
25
|
+
|
|
26
|
+
if record.respond_to?(:user_id)
|
|
27
|
+
record.user_id == user.id
|
|
28
|
+
elsif record.respond_to?(:user)
|
|
29
|
+
record.user == user
|
|
30
|
+
elsif record.respond_to?(:owner_id)
|
|
31
|
+
record.owner_id == user.id
|
|
32
|
+
elsif record.respond_to?(:owner)
|
|
33
|
+
record.owner == user
|
|
34
|
+
else
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if the user is the owner or an admin
|
|
40
|
+
def owner_or_admin?
|
|
41
|
+
owner? || admin?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if the user is the owner or a contributor
|
|
45
|
+
def owner_or_contributor?
|
|
46
|
+
owner? || contributor?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Common pattern: owners and admins can modify
|
|
50
|
+
def can_modify?
|
|
51
|
+
owner_or_admin?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Common pattern: anyone can view, but only owners/admins can modify
|
|
55
|
+
def standard_permissions
|
|
56
|
+
{
|
|
57
|
+
show: true,
|
|
58
|
+
create: logged_in?,
|
|
59
|
+
update: owner_or_admin?,
|
|
60
|
+
destroy: owner_or_admin?
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAuthorize
|
|
4
|
+
module PolicyModules
|
|
5
|
+
# Provides publishing workflow authorization methods
|
|
6
|
+
#
|
|
7
|
+
# Include this module for content that has draft/published states:
|
|
8
|
+
#
|
|
9
|
+
# class ArticlePolicy < SimpleAuthorize::Policy
|
|
10
|
+
# include SimpleAuthorize::PolicyModules::Publishable
|
|
11
|
+
#
|
|
12
|
+
# def show?
|
|
13
|
+
# published? || can_preview?
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# This module assumes your record responds to `published?` or has a
|
|
18
|
+
# `published` boolean field, and optionally `published_at` timestamp.
|
|
19
|
+
module Publishable
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
# Check if the record is published
|
|
23
|
+
def published?
|
|
24
|
+
return false unless record
|
|
25
|
+
|
|
26
|
+
if record.respond_to?(:published?)
|
|
27
|
+
record.published?
|
|
28
|
+
elsif record.respond_to?(:published)
|
|
29
|
+
record.published == true
|
|
30
|
+
elsif record.respond_to?(:status)
|
|
31
|
+
record.status == "published"
|
|
32
|
+
elsif record.respond_to?(:published_at)
|
|
33
|
+
record.published_at && record.published_at <= Time.current
|
|
34
|
+
else
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if the record is a draft
|
|
40
|
+
def draft?
|
|
41
|
+
!published?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if user can publish content
|
|
45
|
+
def can_publish?
|
|
46
|
+
admin? || (contributor? && owner?)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Check if user can unpublish content
|
|
50
|
+
def can_unpublish?
|
|
51
|
+
admin? || (owner? && contributor?)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if user can preview unpublished content
|
|
55
|
+
def can_preview?
|
|
56
|
+
owner? || admin? || contributor?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check if user can schedule publication
|
|
60
|
+
def can_schedule?
|
|
61
|
+
return false unless record.respond_to?(:scheduled_at) || record.respond_to?(:publish_at)
|
|
62
|
+
|
|
63
|
+
can_publish?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Filter attributes based on published state
|
|
67
|
+
def publishable_visible_attributes(base_attributes = [])
|
|
68
|
+
if published? || can_preview?
|
|
69
|
+
base_attributes
|
|
70
|
+
else
|
|
71
|
+
base_attributes - sensitive_draft_attributes
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Attributes that should be hidden in draft state
|
|
76
|
+
def sensitive_draft_attributes
|
|
77
|
+
%i[internal_notes draft_notes review_comments]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAuthorize
|
|
4
|
+
module PolicyModules
|
|
5
|
+
# Provides soft deletion authorization methods
|
|
6
|
+
#
|
|
7
|
+
# Include this module for records that support soft deletion:
|
|
8
|
+
#
|
|
9
|
+
# class CommentPolicy < SimpleAuthorize::Policy
|
|
10
|
+
# include SimpleAuthorize::PolicyModules::SoftDeletable
|
|
11
|
+
#
|
|
12
|
+
# def destroy?
|
|
13
|
+
# soft_deletable? && (owner? || admin?)
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Assumes records have a deleted_at timestamp or similar field.
|
|
18
|
+
module SoftDeletable
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
# Check if the record is soft deleted
|
|
22
|
+
def soft_deleted?
|
|
23
|
+
return false unless record
|
|
24
|
+
|
|
25
|
+
if record.respond_to?(:deleted?)
|
|
26
|
+
record.deleted?
|
|
27
|
+
elsif record.respond_to?(:deleted_at)
|
|
28
|
+
record.deleted_at.present?
|
|
29
|
+
elsif record.respond_to?(:trashed?)
|
|
30
|
+
record.trashed?
|
|
31
|
+
elsif record.respond_to?(:archived?)
|
|
32
|
+
record.archived?
|
|
33
|
+
else
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check if the record is not soft deleted
|
|
39
|
+
def not_deleted?
|
|
40
|
+
!soft_deleted?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if the record supports soft deletion
|
|
44
|
+
def soft_deletable?
|
|
45
|
+
record && (
|
|
46
|
+
record.respond_to?(:deleted_at) ||
|
|
47
|
+
record.respond_to?(:deleted?) ||
|
|
48
|
+
record.respond_to?(:trash!) ||
|
|
49
|
+
record.respond_to?(:archive!)
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check if user can restore soft deleted records
|
|
54
|
+
def can_restore?
|
|
55
|
+
soft_deleted? && (admin? || (owner? && within_restore_window?))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check if user can permanently delete
|
|
59
|
+
def can_permanently_destroy?
|
|
60
|
+
admin?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check if we're within the restore window (default 30 days)
|
|
64
|
+
def within_restore_window?(days = 30)
|
|
65
|
+
return true unless soft_deleted?
|
|
66
|
+
return true unless record.respond_to?(:deleted_at)
|
|
67
|
+
|
|
68
|
+
record.deleted_at > days.days.ago
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if user can view soft deleted records
|
|
72
|
+
def can_view_deleted?
|
|
73
|
+
admin? || (owner? && within_restore_window?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Standard destroy that respects soft delete
|
|
77
|
+
def safe_destroy?
|
|
78
|
+
if soft_deletable?
|
|
79
|
+
# Soft delete if supported
|
|
80
|
+
owner_or_admin?
|
|
81
|
+
else
|
|
82
|
+
# Hard delete requires admin
|
|
83
|
+
admin?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleAuthorize
|
|
4
|
+
module PolicyModules
|
|
5
|
+
# Provides time-based authorization methods
|
|
6
|
+
#
|
|
7
|
+
# Include this module for time-sensitive permissions:
|
|
8
|
+
#
|
|
9
|
+
# class EventPolicy < SimpleAuthorize::Policy
|
|
10
|
+
# include SimpleAuthorize::PolicyModules::Timestamped
|
|
11
|
+
#
|
|
12
|
+
# def update?
|
|
13
|
+
# not_expired? && (owner? || admin?)
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Works with records that have timestamp fields like expired_at,
|
|
18
|
+
# starts_at, ends_at, valid_until, etc.
|
|
19
|
+
module Timestamped
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
# Check if the record has expired
|
|
23
|
+
def expired?
|
|
24
|
+
return false unless record
|
|
25
|
+
|
|
26
|
+
if record.respond_to?(:expired_at)
|
|
27
|
+
record.expired_at && record.expired_at < Time.current
|
|
28
|
+
elsif record.respond_to?(:expires_at)
|
|
29
|
+
record.expires_at && record.expires_at < Time.current
|
|
30
|
+
elsif record.respond_to?(:valid_until)
|
|
31
|
+
record.valid_until && record.valid_until < Time.current
|
|
32
|
+
elsif record.respond_to?(:ends_at)
|
|
33
|
+
record.ends_at && record.ends_at < Time.current
|
|
34
|
+
else
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if the record is not expired
|
|
40
|
+
def not_expired?
|
|
41
|
+
!expired?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if the record is active (started but not ended)
|
|
45
|
+
def active?
|
|
46
|
+
started? && !ended?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Check if the record has started
|
|
50
|
+
def started?
|
|
51
|
+
return true unless record
|
|
52
|
+
|
|
53
|
+
if record.respond_to?(:starts_at)
|
|
54
|
+
record.starts_at.nil? || record.starts_at <= Time.current
|
|
55
|
+
elsif record.respond_to?(:available_from)
|
|
56
|
+
record.available_from.nil? || record.available_from <= Time.current
|
|
57
|
+
elsif record.respond_to?(:valid_from)
|
|
58
|
+
record.valid_from.nil? || record.valid_from <= Time.current
|
|
59
|
+
else
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if the record has ended
|
|
65
|
+
def ended?
|
|
66
|
+
expired?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if record is within a time window
|
|
70
|
+
def within_time_window?
|
|
71
|
+
started? && not_expired?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if the record is locked (cannot be modified)
|
|
75
|
+
def locked?
|
|
76
|
+
return false unless record
|
|
77
|
+
|
|
78
|
+
if record.respond_to?(:locked_at)
|
|
79
|
+
record.locked_at && record.locked_at < Time.current
|
|
80
|
+
elsif record.respond_to?(:locked?)
|
|
81
|
+
record.locked?
|
|
82
|
+
elsif record.respond_to?(:frozen_at)
|
|
83
|
+
record.frozen_at && record.frozen_at < Time.current
|
|
84
|
+
else
|
|
85
|
+
false
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Check if record can be modified (not locked or expired)
|
|
90
|
+
def can_modify_time_based?
|
|
91
|
+
!locked? && not_expired?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Business hours check (useful with context)
|
|
95
|
+
def within_business_hours?(time = Time.current)
|
|
96
|
+
hour = time.hour
|
|
97
|
+
weekday = time.wday
|
|
98
|
+
|
|
99
|
+
# Monday-Friday, 9 AM - 5 PM
|
|
100
|
+
weekday.between?(1, 5) && hour >= 9 && hour < 17
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Require all policy modules
|
|
4
|
+
require_relative "policy_modules/ownable"
|
|
5
|
+
require_relative "policy_modules/publishable"
|
|
6
|
+
require_relative "policy_modules/timestamped"
|
|
7
|
+
require_relative "policy_modules/approvable"
|
|
8
|
+
require_relative "policy_modules/soft_deletable"
|
|
9
|
+
|
|
10
|
+
module SimpleAuthorize
|
|
11
|
+
# Collection of reusable policy modules for common authorization patterns
|
|
12
|
+
#
|
|
13
|
+
# These modules can be mixed into your policy classes to add common
|
|
14
|
+
# authorization functionality without duplicating code.
|
|
15
|
+
#
|
|
16
|
+
# Example:
|
|
17
|
+
# class ArticlePolicy < SimpleAuthorize::Policy
|
|
18
|
+
# include SimpleAuthorize::PolicyModules::Ownable
|
|
19
|
+
# include SimpleAuthorize::PolicyModules::Publishable
|
|
20
|
+
#
|
|
21
|
+
# def show?
|
|
22
|
+
# published? || owner_or_admin?
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
module PolicyModules
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/simple_authorize.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "simple_authorize/version"
|
|
|
5
5
|
require_relative "simple_authorize/configuration"
|
|
6
6
|
require_relative "simple_authorize/controller"
|
|
7
7
|
require_relative "simple_authorize/policy"
|
|
8
|
+
require_relative "simple_authorize/policy_modules"
|
|
8
9
|
require_relative "simple_authorize/test_helpers"
|
|
9
10
|
|
|
10
11
|
# SimpleAuthorize provides a lightweight authorization framework for Rails applications
|
data/spec/examples.txt
CHANGED
|
@@ -3,49 +3,49 @@ example_id | status | run_time |
|
|
|
3
3
|
./spec/rspec_matchers_spec.rb[1:1:1:1] | passed | 0.00003 seconds |
|
|
4
4
|
./spec/rspec_matchers_spec.rb[1:1:1:2] | passed | 0.00003 seconds |
|
|
5
5
|
./spec/rspec_matchers_spec.rb[1:1:1:3] | passed | 0.00004 seconds |
|
|
6
|
-
./spec/rspec_matchers_spec.rb[1:1:2:1] | passed | 0.
|
|
6
|
+
./spec/rspec_matchers_spec.rb[1:1:2:1] | passed | 0.00003 seconds |
|
|
7
7
|
./spec/rspec_matchers_spec.rb[1:1:2:2] | passed | 0.00004 seconds |
|
|
8
8
|
./spec/rspec_matchers_spec.rb[1:1:3:1] | passed | 0.00004 seconds |
|
|
9
9
|
./spec/rspec_matchers_spec.rb[1:1:3:2] | passed | 0.00004 seconds |
|
|
10
10
|
./spec/rspec_matchers_spec.rb[1:1:4:1] | passed | 0.00003 seconds |
|
|
11
|
-
./spec/rspec_matchers_spec.rb[1:1:4:2] | passed | 0.
|
|
12
|
-
./spec/rspec_matchers_spec.rb[1:2:1:1] | passed | 0.
|
|
11
|
+
./spec/rspec_matchers_spec.rb[1:1:4:2] | passed | 0.00004 seconds |
|
|
12
|
+
./spec/rspec_matchers_spec.rb[1:2:1:1] | passed | 0.00004 seconds |
|
|
13
13
|
./spec/rspec_matchers_spec.rb[1:2:1:2] | passed | 0.00004 seconds |
|
|
14
|
-
./spec/rspec_matchers_spec.rb[1:2:2:1] | passed | 0.
|
|
15
|
-
./spec/rspec_matchers_spec.rb[1:2:2:2] | passed | 0.
|
|
14
|
+
./spec/rspec_matchers_spec.rb[1:2:2:1] | passed | 0.00005 seconds |
|
|
15
|
+
./spec/rspec_matchers_spec.rb[1:2:2:2] | passed | 0.00014 seconds |
|
|
16
16
|
./spec/rspec_matchers_spec.rb[1:2:3:1] | passed | 0.00004 seconds |
|
|
17
|
-
./spec/rspec_matchers_spec.rb[1:2:3:2] | passed | 0.
|
|
18
|
-
./spec/rspec_matchers_spec.rb[1:2:4:1] | passed | 0.
|
|
19
|
-
./spec/rspec_matchers_spec.rb[1:2:4:2] | passed | 0.
|
|
20
|
-
./spec/rspec_matchers_spec.rb[1:3:1:1] | passed | 0.
|
|
21
|
-
./spec/rspec_matchers_spec.rb[1:3:1:2] | passed | 0.
|
|
17
|
+
./spec/rspec_matchers_spec.rb[1:2:3:2] | passed | 0.00006 seconds |
|
|
18
|
+
./spec/rspec_matchers_spec.rb[1:2:4:1] | passed | 0.00004 seconds |
|
|
19
|
+
./spec/rspec_matchers_spec.rb[1:2:4:2] | passed | 0.00004 seconds |
|
|
20
|
+
./spec/rspec_matchers_spec.rb[1:3:1:1] | passed | 0.00028 seconds |
|
|
21
|
+
./spec/rspec_matchers_spec.rb[1:3:1:2] | passed | 0.00028 seconds |
|
|
22
22
|
./spec/rspec_matchers_spec.rb[1:3:1:3] | passed | 0.00005 seconds |
|
|
23
|
-
./spec/rspec_matchers_spec.rb[1:3:2:1] | passed | 0.
|
|
23
|
+
./spec/rspec_matchers_spec.rb[1:3:2:1] | passed | 0.00034 seconds |
|
|
24
24
|
./spec/rspec_matchers_spec.rb[1:3:3:1] | passed | 0.00004 seconds |
|
|
25
|
-
./spec/rspec_matchers_spec.rb[1:3:3:2] | passed | 0.
|
|
26
|
-
./spec/rspec_matchers_spec.rb[1:3:4:1] | passed | 0.
|
|
25
|
+
./spec/rspec_matchers_spec.rb[1:3:3:2] | passed | 0.00027 seconds |
|
|
26
|
+
./spec/rspec_matchers_spec.rb[1:3:4:1] | passed | 0.0004 seconds |
|
|
27
27
|
./spec/rspec_matchers_spec.rb[1:3:4:2] | passed | 0.00003 seconds |
|
|
28
|
-
./spec/rspec_matchers_spec.rb[1:4:1:1] | passed | 0.
|
|
28
|
+
./spec/rspec_matchers_spec.rb[1:4:1:1] | passed | 0.00004 seconds |
|
|
29
29
|
./spec/rspec_matchers_spec.rb[1:4:2:1] | passed | 0.00003 seconds |
|
|
30
30
|
./spec/rspec_matchers_spec.rb[1:4:2:2] | passed | 0.00004 seconds |
|
|
31
31
|
./spec/rspec_matchers_spec.rb[1:4:3:1] | passed | 0.00004 seconds |
|
|
32
32
|
./spec/rspec_matchers_spec.rb[1:4:4:1] | passed | 0.00003 seconds |
|
|
33
33
|
./spec/rspec_matchers_spec.rb[1:4:4:2] | passed | 0.00003 seconds |
|
|
34
|
-
./spec/rspec_matchers_spec.rb[1:5:1:1] | passed | 0.
|
|
34
|
+
./spec/rspec_matchers_spec.rb[1:5:1:1] | passed | 0.00004 seconds |
|
|
35
35
|
./spec/rspec_matchers_spec.rb[1:5:1:2] | passed | 0.00003 seconds |
|
|
36
36
|
./spec/rspec_matchers_spec.rb[1:5:2:1] | passed | 0.00004 seconds |
|
|
37
|
-
./spec/rspec_matchers_spec.rb[1:5:3:1] | passed | 0.
|
|
38
|
-
./spec/rspec_matchers_spec.rb[1:5:3:2] | passed | 0.
|
|
37
|
+
./spec/rspec_matchers_spec.rb[1:5:3:1] | passed | 0.00006 seconds |
|
|
38
|
+
./spec/rspec_matchers_spec.rb[1:5:3:2] | passed | 0.00004 seconds |
|
|
39
39
|
./spec/rspec_matchers_spec.rb[1:5:4:1] | passed | 0.00003 seconds |
|
|
40
|
-
./spec/rspec_matchers_spec.rb[1:5:4:2] | passed | 0.
|
|
41
|
-
./spec/rspec_matchers_spec.rb[1:6:1:1] | passed | 0.
|
|
42
|
-
./spec/rspec_matchers_spec.rb[1:6:2:1] | passed | 0.
|
|
40
|
+
./spec/rspec_matchers_spec.rb[1:5:4:2] | passed | 0.00004 seconds |
|
|
41
|
+
./spec/rspec_matchers_spec.rb[1:6:1:1] | passed | 0.0004 seconds |
|
|
42
|
+
./spec/rspec_matchers_spec.rb[1:6:2:1] | passed | 0.00037 seconds |
|
|
43
43
|
./spec/rspec_matchers_spec.rb[1:6:2:2] | passed | 0.00004 seconds |
|
|
44
44
|
./spec/rspec_matchers_spec.rb[1:6:3:1] | passed | 0.00004 seconds |
|
|
45
45
|
./spec/rspec_matchers_spec.rb[1:6:3:2] | passed | 0.00004 seconds |
|
|
46
46
|
./spec/rspec_matchers_spec.rb[1:6:4:1] | passed | 0.00003 seconds |
|
|
47
|
-
./spec/rspec_matchers_spec.rb[1:6:4:2] | passed | 0.
|
|
47
|
+
./spec/rspec_matchers_spec.rb[1:6:4:2] | passed | 0.00035 seconds |
|
|
48
48
|
./spec/rspec_matchers_spec.rb[1:7:1:1] | passed | 0.00004 seconds |
|
|
49
|
-
./spec/rspec_matchers_spec.rb[1:7:1:2] | passed | 0.
|
|
50
|
-
./spec/rspec_matchers_spec.rb[1:7:1:3] | passed | 0.
|
|
51
|
-
./spec/rspec_matchers_spec.rb[1:7:2:1] | passed | 0.
|
|
49
|
+
./spec/rspec_matchers_spec.rb[1:7:1:2] | passed | 0.00003 seconds |
|
|
50
|
+
./spec/rspec_matchers_spec.rb[1:7:1:3] | passed | 0.00035 seconds |
|
|
51
|
+
./spec/rspec_matchers_spec.rb[1:7:2:1] | passed | 0.00128 seconds |
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple_authorize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -92,6 +92,7 @@ files:
|
|
|
92
92
|
- ".overcommit.yml"
|
|
93
93
|
- ".simplecov"
|
|
94
94
|
- CHANGELOG.md
|
|
95
|
+
- CLAUDE.md
|
|
95
96
|
- CODE_OF_CONDUCT.md
|
|
96
97
|
- CONTRIBUTING.md
|
|
97
98
|
- LICENSE.txt
|
|
@@ -110,6 +111,12 @@ files:
|
|
|
110
111
|
- lib/simple_authorize/configuration.rb
|
|
111
112
|
- lib/simple_authorize/controller.rb
|
|
112
113
|
- lib/simple_authorize/policy.rb
|
|
114
|
+
- lib/simple_authorize/policy_modules.rb
|
|
115
|
+
- lib/simple_authorize/policy_modules/approvable.rb
|
|
116
|
+
- lib/simple_authorize/policy_modules/ownable.rb
|
|
117
|
+
- lib/simple_authorize/policy_modules/publishable.rb
|
|
118
|
+
- lib/simple_authorize/policy_modules/soft_deletable.rb
|
|
119
|
+
- lib/simple_authorize/policy_modules/timestamped.rb
|
|
113
120
|
- lib/simple_authorize/railtie.rb
|
|
114
121
|
- lib/simple_authorize/rspec.rb
|
|
115
122
|
- lib/simple_authorize/test_helpers.rb
|