rhino-rails 4.3.0 → 4.4.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/README.md +1 -0
- data/lib/rhino/concerns/has_rhino.rb +21 -0
- data/lib/rhino/controllers/resources_controller.rb +7 -3
- data/lib/rhino/models/rhino_model.rb +30 -0
- data/lib/rhino/query_builder.rb +38 -1
- data/lib/rhino/scope_not_allowed_error.rb +7 -0
- data/lib/rhino/version.rb +1 -1
- data/lib/rhino.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0155b30eedf93061b38295ed96254194529de0abf03aad309becc312a512c5ba
|
|
4
|
+
data.tar.gz: 5e1ce1624bab2a77f8603ebdf6984a43e4bd71d277f51900ae533465e4fd0881
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 975f1ee3efc83423ae775131eace5689a7b09269541848da21e286f2f55cedd71b265a8e3c5c448a85cc7b9c8ecdde2e27dd615ef1a26719732a7d958cbfb8da
|
|
7
|
+
data.tar.gz: 3a32ccaf04ad8f351828752a83efac31503d53afbd4c3239d0f38e836c8e151dd4681f2a650a853e9d6d728e6477f34e72023680c6bbd36dc63369ad6ca6d10c
|
data/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Register a model, get a full REST API instantly.
|
|
|
40
40
|
| 26 | **Generator CLI** | `rhino:install`, `rhino:generate`, `rhino:blueprint`, `rhino:export_postman`. |
|
|
41
41
|
| 27 | **Postman Export** | Auto-generated Postman Collection v2.1 with all endpoints. |
|
|
42
42
|
| 28 | **Blueprint System** | YAML-to-code generation for models, migrations, factories, policies, tests, and seeders. |
|
|
43
|
+
| 29 | **Named Scopes** | `?scope=availableForDrivers` client-selectable scopes (whitelisted via `rhino_scopes`), plus a `rhino_default_scope` applied when none is requested. Unknown scopes return 403. Applies to `index`/`trashed` only. |
|
|
43
44
|
|
|
44
45
|
## Quick Start
|
|
45
46
|
|
|
@@ -13,6 +13,8 @@ module Rhino
|
|
|
13
13
|
# rhino_includes :user, :comments
|
|
14
14
|
# rhino_fields :id, :title, :status, :created_at
|
|
15
15
|
# rhino_search :title, :content, 'user.name'
|
|
16
|
+
# rhino_scopes :active, available_for_drivers: Scopes::AvailableForDriversScope
|
|
17
|
+
# rhino_default_scope :active
|
|
16
18
|
# rhino_per_page 25
|
|
17
19
|
# rhino_pagination_enabled true
|
|
18
20
|
# rhino_middleware 'throttle:60,1'
|
|
@@ -24,6 +26,8 @@ module Rhino
|
|
|
24
26
|
|
|
25
27
|
included do
|
|
26
28
|
class_attribute :allowed_filters, default: []
|
|
29
|
+
class_attribute :allowed_scopes, default: {}
|
|
30
|
+
class_attribute :default_rhino_scope, default: nil
|
|
27
31
|
class_attribute :allowed_sorts, default: []
|
|
28
32
|
class_attribute :default_sort_field, default: nil
|
|
29
33
|
class_attribute :allowed_includes, default: []
|
|
@@ -42,6 +46,23 @@ module Rhino
|
|
|
42
46
|
self.allowed_filters = fields.map(&:to_s)
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
# Whitelist client-selectable named scopes for ?scope=.
|
|
50
|
+
# rhino_scopes :active, available_for_drivers: Scopes::AvailableForDriversScope
|
|
51
|
+
# Bare symbols must name an existing ActiveRecord scope/class method on the model.
|
|
52
|
+
# Hash values may be a Proc(relation, user) or a Rhino::ResourceScope subclass.
|
|
53
|
+
def rhino_scopes(*names, **named)
|
|
54
|
+
merged = allowed_scopes.dup
|
|
55
|
+
names.each { |n| merged[n.to_s] = n.to_sym }
|
|
56
|
+
named.each { |k, v| merged[k.to_s] = v }
|
|
57
|
+
self.allowed_scopes = merged
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Named scope applied when no ?scope param is sent. Convenience, not a
|
|
61
|
+
# security boundary. Value is the scope name (string/symbol).
|
|
62
|
+
def rhino_default_scope(name)
|
|
63
|
+
self.default_rhino_scope = name.to_s
|
|
64
|
+
end
|
|
65
|
+
|
|
45
66
|
def rhino_sorts(*fields)
|
|
46
67
|
self.allowed_sorts = fields.map(&:to_s)
|
|
47
68
|
end
|
|
@@ -16,6 +16,10 @@ module Rhino
|
|
|
16
16
|
render json: { message: "This action is unauthorized." }, status: :forbidden
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
rescue_from Rhino::ScopeNotAllowedError do |e|
|
|
20
|
+
render json: { message: "Scope '#{e.message}' is not allowed" }, status: :forbidden
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
# Cache for auto-detected organization paths (class-level, survives across requests)
|
|
20
24
|
@@organization_path_cache = {}
|
|
21
25
|
|
|
@@ -38,7 +42,7 @@ module Rhino
|
|
|
38
42
|
def index
|
|
39
43
|
authorize model_class, :index?, policy_class: policy_for(model_class)
|
|
40
44
|
|
|
41
|
-
builder = QueryBuilder.new(model_class, params: params)
|
|
45
|
+
builder = QueryBuilder.new(model_class, params: params, named_scopes: true)
|
|
42
46
|
apply_organization_scope(builder)
|
|
43
47
|
builder.build
|
|
44
48
|
|
|
@@ -170,7 +174,7 @@ module Rhino
|
|
|
170
174
|
def trashed
|
|
171
175
|
authorize model_class, :view_trashed?, policy_class: policy_for(model_class)
|
|
172
176
|
|
|
173
|
-
builder = QueryBuilder.new(model_class.discarded, params: params)
|
|
177
|
+
builder = QueryBuilder.new(model_class.discarded, params: params, named_scopes: true)
|
|
174
178
|
apply_organization_scope(builder)
|
|
175
179
|
builder.build
|
|
176
180
|
|
|
@@ -900,7 +904,7 @@ module Rhino
|
|
|
900
904
|
end
|
|
901
905
|
|
|
902
906
|
def params_hash
|
|
903
|
-
params.except(:controller, :action, :model_slug, :route_group, :organization, :id, :format).to_unsafe_h
|
|
907
|
+
params.except(:controller, :action, :model_slug, :route_group, :organization, :id, :format, :scope).to_unsafe_h
|
|
904
908
|
end
|
|
905
909
|
end
|
|
906
910
|
end
|
|
@@ -104,6 +104,36 @@ module Rhino
|
|
|
104
104
|
# self.allowed_filters = %w[status user_id category_id]
|
|
105
105
|
self.allowed_filters = []
|
|
106
106
|
|
|
107
|
+
# @!attribute [rw] allowed_scopes
|
|
108
|
+
# Client-selectable named scopes (whitelist for +?scope=+).
|
|
109
|
+
#
|
|
110
|
+
# Controls which named scopes can be requested via +?scope=name+
|
|
111
|
+
# (camelCase on the wire, underscored internally). Only whitelisted
|
|
112
|
+
# scopes are accepted — unknown/unlisted names return 403.
|
|
113
|
+
#
|
|
114
|
+
# Set via DSL: +rhino_scopes :active, available_for_drivers: Scopes::AvailableForDriversScope+
|
|
115
|
+
#
|
|
116
|
+
# Query: +GET /api/posts?scope=availableForDrivers+
|
|
117
|
+
#
|
|
118
|
+
# @return [Hash{String => Symbol, Proc, Class}]
|
|
119
|
+
# @example
|
|
120
|
+
# rhino_scopes :active, available_for_drivers: Scopes::AvailableForDriversScope
|
|
121
|
+
self.allowed_scopes = {}
|
|
122
|
+
|
|
123
|
+
# @!attribute [rw] default_rhino_scope
|
|
124
|
+
# Named scope applied automatically when no +?scope+ param is sent.
|
|
125
|
+
#
|
|
126
|
+
# This is a listing convenience, not a security boundary. The default
|
|
127
|
+
# scope is always requestable by name even if it is not otherwise
|
|
128
|
+
# whitelisted via +rhino_scopes+.
|
|
129
|
+
#
|
|
130
|
+
# Set via DSL: +rhino_default_scope :active+
|
|
131
|
+
#
|
|
132
|
+
# @return [String, nil]
|
|
133
|
+
# @example
|
|
134
|
+
# rhino_default_scope :active
|
|
135
|
+
self.default_rhino_scope = nil
|
|
136
|
+
|
|
107
137
|
# @!attribute [rw] allowed_sorts
|
|
108
138
|
# Sortable columns.
|
|
109
139
|
#
|
data/lib/rhino/query_builder.rb
CHANGED
|
@@ -14,14 +14,16 @@ module Rhino
|
|
|
14
14
|
class QueryBuilder
|
|
15
15
|
attr_reader :scope, :model_class, :params
|
|
16
16
|
|
|
17
|
-
def initialize(model_class, params: {})
|
|
17
|
+
def initialize(model_class, params: {}, named_scopes: false)
|
|
18
18
|
@model_class = model_class
|
|
19
19
|
@scope = model_class.all
|
|
20
20
|
@params = params
|
|
21
|
+
@named_scopes = named_scopes
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
# Apply all query modifications based on params and model config.
|
|
24
25
|
def build
|
|
26
|
+
apply_named_scope if @named_scopes
|
|
25
27
|
apply_filters
|
|
26
28
|
apply_default_sort
|
|
27
29
|
apply_sorts
|
|
@@ -62,6 +64,41 @@ module Rhino
|
|
|
62
64
|
|
|
63
65
|
private
|
|
64
66
|
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Named scopes: ?scope=availableForDrivers
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
#
|
|
71
|
+
# Only runs for collection endpoints (index/trashed), which pass
|
|
72
|
+
# +named_scopes: true+. `show` (including its ?include= build path) stays
|
|
73
|
+
# unscoped so a record excluded by the default scope is still viewable.
|
|
74
|
+
def apply_named_scope
|
|
75
|
+
requested = params[:scope].presence
|
|
76
|
+
name = requested ? requested.to_s.underscore : model_class.try(:default_rhino_scope)
|
|
77
|
+
return unless name
|
|
78
|
+
|
|
79
|
+
allowed = model_class.try(:allowed_scopes) || {}
|
|
80
|
+
entry = allowed[name]
|
|
81
|
+
# The default scope is implicitly allowed when requested by name.
|
|
82
|
+
entry ||= name.to_sym if name == model_class.try(:default_rhino_scope)
|
|
83
|
+
|
|
84
|
+
# Echo the client's wire name (not the underscored form) in the error.
|
|
85
|
+
raise Rhino::ScopeNotAllowedError, (requested ? requested.to_s : name) if entry.nil?
|
|
86
|
+
|
|
87
|
+
user = defined?(RequestStore) ? RequestStore.store[:rhino_current_user] : nil
|
|
88
|
+
|
|
89
|
+
@scope =
|
|
90
|
+
case entry
|
|
91
|
+
when Symbol
|
|
92
|
+
# Whitelisted AR scope. Client input never reaches public_send unless the
|
|
93
|
+
# developer declared it via rhino_scopes. .merge composes with default_scopes.
|
|
94
|
+
@scope.merge(model_class.public_send(entry))
|
|
95
|
+
when Proc
|
|
96
|
+
entry.call(@scope, user)
|
|
97
|
+
else
|
|
98
|
+
entry.new.apply(@scope) # Rhino::ResourceScope subclass (user/org/role helpers)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
65
102
|
# ------------------------------------------------------------------
|
|
66
103
|
# Filtering: ?filter[status]=published&filter[user_id]=1
|
|
67
104
|
# ------------------------------------------------------------------
|
data/lib/rhino/version.rb
CHANGED
data/lib/rhino.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rhino-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bruno Cipolla
|
|
@@ -235,6 +235,7 @@ files:
|
|
|
235
235
|
- lib/rhino/routes.rb
|
|
236
236
|
- lib/rhino/routing/domain_constraint.rb
|
|
237
237
|
- lib/rhino/routing/route_group_validator.rb
|
|
238
|
+
- lib/rhino/scope_not_allowed_error.rb
|
|
238
239
|
- lib/rhino/tasks/rhino.rake
|
|
239
240
|
- lib/rhino/templates/audit_trail/create_audit_logs.rb.erb
|
|
240
241
|
- lib/rhino/templates/generate/factory.rb.erb
|