simple_authorize 1.0.1 → 1.1.0
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 +19 -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/version.rb +1 -1
- data/lib/simple_authorize.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1e7ec7dc4e13ab781a1379671cb043b2a03956c29e32554ff00624ee57cd009a
|
|
4
|
+
data.tar.gz: d0fb3ae9757d4ccd93250a09847630e5f4958c32498bff21047cf1e903cc4c19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca8717f9564cdbe1fe7873e7c5c7f10452a8825d83170565f306272e9b5cfcbd18adf181081e3603c58e206eb743e6ee4b4b8d1a4fd477a11d5946a5bd793fea
|
|
7
|
+
data.tar.gz: cbe20818849f24ee61dec25ec75f08ba22c1bf75f5c9c2c1021cdaea3548aa01b084e009d16391c60568897148f13d2ba95e1c2817ae6c9697dea29fec9983a8
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.0] - 2025-11-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
#### Policy Composition
|
|
13
|
+
- **Reusable Policy Modules** - Built-in modules for common authorization patterns
|
|
14
|
+
- **Ownable Module** - Ownership-based authorization helpers
|
|
15
|
+
- **Publishable Module** - Publishing workflow authorization
|
|
16
|
+
- **Timestamped Module** - Time-based access control
|
|
17
|
+
- **Approvable Module** - Approval workflow helpers
|
|
18
|
+
- **SoftDeletable Module** - Soft deletion authorization
|
|
19
|
+
- **Custom Module Support** - Easy creation of custom authorization modules
|
|
20
|
+
|
|
21
|
+
#### Context-Aware Policies
|
|
22
|
+
- **Request Context** - Pass additional context to policies (IP, time, location, etc.)
|
|
23
|
+
- **Controller Integration** - `authorization_context` method for building context
|
|
24
|
+
- **Context in Scopes** - Context available in Policy::Scope classes
|
|
25
|
+
- **Common Patterns** - Built-in support for geographic restrictions, time-based access, rate limiting
|
|
26
|
+
|
|
8
27
|
## [1.0.0] - 2025-11-03
|
|
9
28
|
|
|
10
29
|
### Added
|
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
|
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
|
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.0
|
|
4
|
+
version: 1.1.0
|
|
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
|