spree_api 5.6.0.rc5 → 5.6.0.rc6
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/app/controllers/concerns/spree/api/v3/http_caching.rb +27 -6
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +48 -1
- data/app/controllers/spree/api/v3/admin/base_controller.rb +5 -1
- data/app/controllers/spree/api/v3/admin/tags_controller.rb +13 -4
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f86ce456fb13e7e6f1fac5a39a1ad7f0a4befe5dc959a88c80b3bcaa3689b57
|
|
4
|
+
data.tar.gz: 4df7f39849201749a3b5a26337a009a36ab0b9fc4b4588d5d90accf859368ec7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7fa240df22e6815d24910c37e32b873a8dae7a4e0ded6c00430e7d895e287ae3ae6e78cb11fcbb52d4930f876e48e22bf8be7353eb317722789772a17ceb6aa9
|
|
7
|
+
data.tar.gz: b4623924dc7d63bb2abae38790fa31c5140392e6838cce8c1c530fd6314110856fcf55c8c5c8a2c01a81fff58a1a7106787a3f3615d1b0f0dd16af4b911c027c
|
|
@@ -22,10 +22,13 @@ module Spree
|
|
|
22
22
|
current_user.nil?
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
# Set Vary headers to ensure proper CDN caching by currency/locale
|
|
25
|
+
# Set Vary headers to ensure proper CDN caching by currency/locale/channel.
|
|
26
|
+
# X-Spree-Channel is included because the resolved channel changes the
|
|
27
|
+
# serialized body (price hiding + channel-scoped visibility), so a shared
|
|
28
|
+
# cache must not serve one channel's guest response to another channel.
|
|
26
29
|
def set_vary_headers
|
|
27
30
|
if guest_user?
|
|
28
|
-
response.headers['Vary'] = 'Accept, x-spree-currency, x-spree-locale'
|
|
31
|
+
response.headers['Vary'] = 'Accept, x-spree-currency, x-spree-locale, x-spree-channel'
|
|
29
32
|
else
|
|
30
33
|
response.headers['Cache-Control'] = 'private, no-store'
|
|
31
34
|
end
|
|
@@ -67,14 +70,21 @@ module Spree
|
|
|
67
70
|
|
|
68
71
|
expires_in expires_in, public: true
|
|
69
72
|
|
|
70
|
-
# Use Rails' stale? which handles ETag and Last-Modified
|
|
71
|
-
|
|
73
|
+
# Use Rails' stale? which handles ETag and Last-Modified. The channel
|
|
74
|
+
# is folded into the ETag so a shared cache never serves one channel's
|
|
75
|
+
# guest response to another; Last-Modified mirrors the resource's own
|
|
76
|
+
# timestamp as before.
|
|
77
|
+
stale?(
|
|
78
|
+
etag: [resource, cache_channel_fragment],
|
|
79
|
+
last_modified: resource.try(:updated_at),
|
|
80
|
+
public: true
|
|
81
|
+
)
|
|
72
82
|
end
|
|
73
83
|
|
|
74
84
|
private
|
|
75
85
|
|
|
76
86
|
# Build a cache key for a collection
|
|
77
|
-
# Includes: latest updated_at, total count, query params, pagination, expand, currency, locale
|
|
87
|
+
# Includes: latest updated_at, total count, query params, pagination, expand, currency, locale, channel
|
|
78
88
|
def collection_cache_key(collection)
|
|
79
89
|
# For ActiveRecord collections use updated_at, for plain arrays use store's updated_at as proxy
|
|
80
90
|
latest_updated_at = if collection.first&.respond_to?(:updated_at)
|
|
@@ -92,11 +102,22 @@ module Spree
|
|
|
92
102
|
params[:page],
|
|
93
103
|
params[:limit],
|
|
94
104
|
current_currency,
|
|
95
|
-
current_locale
|
|
105
|
+
current_locale,
|
|
106
|
+
cache_channel_fragment
|
|
96
107
|
]
|
|
97
108
|
|
|
98
109
|
parts.compact.join('/')
|
|
99
110
|
end
|
|
111
|
+
|
|
112
|
+
# Identifies the resolved channel for cache-key purposes. The channel
|
|
113
|
+
# changes the serialized body (price hiding + channel-scoped visibility),
|
|
114
|
+
# so it must be part of the guest cache identity. Uses the same
|
|
115
|
+
# +current_channel+ source of truth as serialization/gating.
|
|
116
|
+
def cache_channel_fragment
|
|
117
|
+
return nil unless respond_to?(:current_channel, true)
|
|
118
|
+
|
|
119
|
+
current_channel&.id
|
|
120
|
+
end
|
|
100
121
|
end
|
|
101
122
|
end
|
|
102
123
|
end
|
|
@@ -56,10 +56,34 @@ module Spree
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
58
|
def authorize_api_key_scope!
|
|
59
|
-
|
|
59
|
+
# Endpoints explicitly exempt from the scope mechanism (auth, me, public
|
|
60
|
+
# invitation acceptance, per-type-filtered exports, etc.) opt out first,
|
|
61
|
+
# before any key resolution or enforcement.
|
|
60
62
|
return if self.class._scope_check_skipped
|
|
61
63
|
return if self.class._scope_check_skipped_actions&.include?(action_name)
|
|
62
64
|
|
|
65
|
+
# Fail CLOSED, not open. A nil `current_api_key` legitimately means a
|
|
66
|
+
# non-secret-key request (JWT admin / publishable) authorized by other
|
|
67
|
+
# means, which correctly skips scope checks. But it ALSO happens when
|
|
68
|
+
# this guard runs before authentication resolved the key: a bare
|
|
69
|
+
# `return unless current_api_key` would then skip scope enforcement for
|
|
70
|
+
# an attacker-supplied secret key. So resolve the secret key in place
|
|
71
|
+
# here — never rely on before_action ordering. Only truly non-secret-key
|
|
72
|
+
# requests (nothing to resolve) are waved through.
|
|
73
|
+
resolve_current_api_key_for_scope_check!
|
|
74
|
+
|
|
75
|
+
unless current_api_key
|
|
76
|
+
return unless secret_key_request?
|
|
77
|
+
|
|
78
|
+
# A secret-key token was presented but did not resolve to a live key
|
|
79
|
+
# for this store — reject rather than silently skip the scope check.
|
|
80
|
+
return render_error(
|
|
81
|
+
code: Spree::Api::V3::ErrorHandler::ERROR_CODES[:invalid_token],
|
|
82
|
+
message: 'Valid secret API key required',
|
|
83
|
+
status: :unauthorized
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
63
87
|
resource = scoped_resource_name
|
|
64
88
|
# Fail closed: a controller authenticated by API key MUST declare
|
|
65
89
|
# either `scoped_resource :name`, override `scoped_resource_name`,
|
|
@@ -77,6 +101,29 @@ module Spree
|
|
|
77
101
|
)
|
|
78
102
|
end
|
|
79
103
|
|
|
104
|
+
# True when the request presents a secret API key token (`sk_` prefix),
|
|
105
|
+
# regardless of whether authentication has resolved it into
|
|
106
|
+
# `current_api_key` yet. Used to keep the scope guard fail-closed even if
|
|
107
|
+
# it runs before the API key is loaded: a secret-key request whose key is
|
|
108
|
+
# still nil must be denied, never waved through.
|
|
109
|
+
def secret_key_request?
|
|
110
|
+
extract_api_key.to_s.start_with?(Spree::ApiKey::PREFIXES['secret'])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Resolves the request's secret API key into `@current_api_key` if a
|
|
114
|
+
# secret-key token is present and it hasn't been resolved yet, so the scope
|
|
115
|
+
# check does not depend on `authenticate_admin!` having already run. Mirrors
|
|
116
|
+
# the secret-key resolution + store binding in AdminAuthentication, and is
|
|
117
|
+
# idempotent: when the key is already loaded (guard runs after auth) it does
|
|
118
|
+
# nothing.
|
|
119
|
+
def resolve_current_api_key_for_scope_check!
|
|
120
|
+
return if @current_api_key
|
|
121
|
+
return unless secret_key_request?
|
|
122
|
+
|
|
123
|
+
key = Spree::ApiKey.find_by_secret_token(extract_api_key)
|
|
124
|
+
@current_api_key = key if key && key.store_id == current_store.id
|
|
125
|
+
end
|
|
126
|
+
|
|
80
127
|
# The resource name used in scope strings (`read_<name>` / `write_<name>`).
|
|
81
128
|
# Defaults to the class-level `scoped_resource :name` declaration.
|
|
82
129
|
# Override in controllers that resolve scope at request time (e.g. the
|
|
@@ -4,9 +4,13 @@ module Spree
|
|
|
4
4
|
module Admin
|
|
5
5
|
class BaseController < Spree::Api::V3::BaseController
|
|
6
6
|
include Spree::Api::V3::AdminAuthentication
|
|
7
|
-
include Spree::Api::V3::ScopedAuthorization
|
|
8
7
|
|
|
8
|
+
# Must be registered before ScopedAuthorization so authenticate_admin!
|
|
9
|
+
# resolves @current_api_key before authorize_api_key_scope! reads it —
|
|
10
|
+
# otherwise the scope guard short-circuits on a nil key and never runs.
|
|
9
11
|
before_action :authenticate_admin!
|
|
12
|
+
|
|
13
|
+
include Spree::Api::V3::ScopedAuthorization
|
|
10
14
|
end
|
|
11
15
|
end
|
|
12
16
|
end
|
|
@@ -60,15 +60,24 @@ module Spree
|
|
|
60
60
|
false
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
# Tagging filter. `Spree::
|
|
64
|
-
#
|
|
65
|
-
#
|
|
63
|
+
# Tagging filter. Store-owned taggables (`Spree::Product`,
|
|
64
|
+
# `Spree::Order`) tenant their taggings by `store_id` via
|
|
65
|
+
# `acts_as_taggable_tenant`, so their vocabulary is bounded to the
|
|
66
|
+
# current store with a single indexed `tenant` filter. Global
|
|
67
|
+
# taggables like the customer/user type carry no store, so their tags
|
|
68
|
+
# are not store-scoped here.
|
|
66
69
|
def taggings_conditions
|
|
67
70
|
conditions = { taggable_type: taggable_type, context: 'tags' }
|
|
68
|
-
conditions[:tenant] = current_store.id.to_s if
|
|
71
|
+
conditions[:tenant] = current_store.id.to_s if store_tenanted_taggable?
|
|
69
72
|
conditions
|
|
70
73
|
end
|
|
71
74
|
|
|
75
|
+
# True for taggables whose taggings carry a store `tenant`
|
|
76
|
+
# (`acts_as_taggable_tenant :store_id`), i.e. store-owned records.
|
|
77
|
+
def store_tenanted_taggable?
|
|
78
|
+
%w[Spree::Product Spree::Order].include?(taggable_type)
|
|
79
|
+
end
|
|
80
|
+
|
|
72
81
|
# Per-type scope check for API-key principals: a key listing product
|
|
73
82
|
# tags needs `read_products`, order tags `read_orders`, etc. JWT
|
|
74
83
|
# admins are gated by store membership + CanCanCan, not scopes.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.6.0.
|
|
4
|
+
version: 5.6.0.rc6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rswag-specs
|
|
@@ -72,14 +72,14 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - '='
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 5.6.0.
|
|
75
|
+
version: 5.6.0.rc6
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - '='
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 5.6.0.
|
|
82
|
+
version: 5.6.0.rc6
|
|
83
83
|
description: Spree's API
|
|
84
84
|
email:
|
|
85
85
|
- hello@spreecommerce.org
|
|
@@ -384,9 +384,9 @@ licenses:
|
|
|
384
384
|
- BSD-3-Clause
|
|
385
385
|
metadata:
|
|
386
386
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
387
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.
|
|
387
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc6
|
|
388
388
|
documentation_uri: https://docs.spreecommerce.org/
|
|
389
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.6.0.
|
|
389
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc6
|
|
390
390
|
post_install_message:
|
|
391
391
|
rdoc_options: []
|
|
392
392
|
require_paths:
|