shopify_api 16.3.0 → 16.3.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/.github/workflows/gardener-notify-slack.yml +5 -46
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/lib/shopify_api/auth/oauth.rb +2 -0
- data/lib/shopify_api/auth.rb +30 -0
- data/lib/shopify_api/utils/shop_validator.rb +11 -2
- data/lib/shopify_api/version.rb +1 -1
- 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: 15b9053636bf36a285462a2843dae8e7603dfcac4429e5e98810bfd0538679b0
|
|
4
|
+
data.tar.gz: d5488b89b1fa4135536142cc729656515ca338c5fd61e30df15d9026d2dd52a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d3a1818a57ffe3be7dc30ca153e63c0e37e2cb2198db19d0d711db2f256857f1fff60307a3c8896b26252964679b9b6170c111debefc45be655eb40cbec3569
|
|
7
|
+
data.tar.gz: ee2bedb0b6fa998ac04fdcba3a4b5927a4c0c626ab2d686cc6aba6c3005944cdf1288a3622a186cc457a49e25715c0afeae9f8f7dd0f195a9d38d8d21011b47e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: Gardener - Notify Slack
|
|
2
2
|
# Runs after `Gardener - Notify Event` completes and does the real work:
|
|
3
|
-
# applies the devtools-gardener label and posts a
|
|
3
|
+
# applies the devtools-gardener label and posts a repo-prefixed linked title to Slack.
|
|
4
4
|
#
|
|
5
5
|
# The workflow_run trigger runs this job in the default-branch context with
|
|
6
6
|
# full GITHUB_TOKEN permissions and Actions secret access — this is what
|
|
@@ -59,57 +59,16 @@ jobs:
|
|
|
59
59
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }}
|
|
60
60
|
SLACK_CHANNEL_ID: ${{ vars.GARDENER_SLACK_CHANNEL_ID }}
|
|
61
61
|
run: |
|
|
62
|
-
KIND=$(jq -r 'if has("pull_request") then "PR" else "Issue" end' event.json)
|
|
63
|
-
# Pull the body out, truncate, then convert GitHub Markdown to
|
|
64
|
-
# Slack mrkdwn. Links and fenced code blocks are stashed before
|
|
65
|
-
# the HTML-escape pass so their contents survive verbatim (a `&`
|
|
66
|
-
# inside a URL must stay raw, and code content shouldn't be
|
|
67
|
-
# mangled). Blockquote `> ` markers are also stashed so the
|
|
68
|
-
# `>` → `>` escape doesn't break them. Everything else is
|
|
69
|
-
# HTML-escaped so user-supplied `<`, `>`, `&` can't collide
|
|
70
|
-
# with Slack link syntax or injected mentions like <!channel>.
|
|
71
|
-
BODY=$(jq -r '(.issue // .pull_request).body // ""' event.json)
|
|
72
|
-
if [ ${#BODY} -gt 1000 ]; then
|
|
73
|
-
BODY="${BODY:0:1000}…"
|
|
74
|
-
fi
|
|
75
|
-
BODY=$(printf '%s' "$BODY" | perl -0777 -pe '
|
|
76
|
-
my @u;
|
|
77
|
-
s{\[([^\]]+)\]\(([^)]+)\)}{push @u, $2; "\x01$#u\x02$1\x03"}ge;
|
|
78
|
-
my @c;
|
|
79
|
-
s{^```[^\n]*\n(.*?)\n```$}{push @c, $1; "\x04$#c\x05"}gems;
|
|
80
|
-
s/^> /\x06/gm;
|
|
81
|
-
s/^#{1,6}\s+(.+)$/*$1*/gm;
|
|
82
|
-
s/\*\*(.+?)\*\*/*$1*/g;
|
|
83
|
-
s/^(\s*)- \[x\]\s+/$1✓ /gm;
|
|
84
|
-
s/^(\s*)[-*]\s+/$1• /gm;
|
|
85
|
-
s/&/&/g;
|
|
86
|
-
s/</</g;
|
|
87
|
-
s/>/>/g;
|
|
88
|
-
s/\x06/> /g;
|
|
89
|
-
s{\x01(\d+)\x02(.*?)\x03}{"<$u[$1]|$2>"}ge;
|
|
90
|
-
s{\x04(\d+)\x05}{"```\n$c[$1]\n```"}ge;
|
|
91
|
-
')
|
|
92
62
|
jq \
|
|
93
63
|
--arg channel "$SLACK_CHANNEL_ID" \
|
|
94
|
-
--arg kind "$KIND" \
|
|
95
|
-
--arg body "$BODY" \
|
|
96
64
|
'
|
|
97
65
|
def escape: gsub("&";"&") | gsub("<";"<") | gsub(">";">");
|
|
98
66
|
|
|
99
67
|
(.issue // .pull_request) as $i
|
|
100
|
-
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
+ (if $i.draft then " · draft" else "" end)
|
|
105
|
-
else "" end) as $meta
|
|
106
|
-
| [ "*<\($i.html_url)|\($kind) #\($i.number)>* — \(($i.title | escape))",
|
|
107
|
-
"_opened by \($i.user.login)\($meta)_" ]
|
|
108
|
-
+ (if $body != "" then [$body] else [] end)
|
|
109
|
-
+ (if $labels != "" then [$labels] else [] end)
|
|
110
|
-
| join("\n") as $msg
|
|
111
|
-
| { channel: $channel, text: "\($kind) #\($i.number): \($i.title)",
|
|
112
|
-
blocks: [{ type: "section", text: { type: "mrkdwn", text: $msg } }] }
|
|
68
|
+
| { channel: $channel,
|
|
69
|
+
text: "\(.repository.name) · <\($i.html_url)|\($i.title | escape)>",
|
|
70
|
+
unfurl_links: false,
|
|
71
|
+
unfurl_media: false }
|
|
113
72
|
' event.json | curl -sf -X POST \
|
|
114
73
|
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
|
|
115
74
|
-H 'Content-type: application/json; charset=utf-8' \
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
Note: For changes to the API, see https://shopify.dev/changelog?filter=api
|
|
4
4
|
## Unreleased
|
|
5
5
|
|
|
6
|
+
## 16.3.1 (2026-10-22)
|
|
7
|
+
- [#1472](https://github.com/Shopify/shopify-api-ruby/pull/1472) `ShopifyAPI::Auth.embedded_app_url` now raises `ShopifyAPI::Errors::InvalidShopError` unless the decoded `host` is a trusted Shopify Admin host (see `ShopifyAPI::Utils::ShopValidator`). URLs for supported Admin hosts are unchanged.
|
|
8
|
+
- [#1472](https://github.com/Shopify/shopify-api-ruby/pull/1472) `ShopifyAPI::Auth::Oauth.begin_auth` now validates and normalizes `shop` with `ShopifyAPI::Utils::ShopValidator.sanitize!`, and raises `ShopifyAPI::Errors::InvalidShopError` for untrusted domains.
|
|
9
|
+
- [#1472](https://github.com/Shopify/shopify-api-ruby/pull/1472) `ShopifyAPI::Utils::ShopValidator` now rejects hosts with non-hostname characters and reads the store handle from the segment after `/store/` in Admin URLs (`admin.shopify.com/store/<handle>`), rejecting a missing or invalid handle.
|
|
10
|
+
|
|
6
11
|
## 16.3.0 (2026-08-04)
|
|
7
12
|
- [#1443](https://github.com/Shopify/shopify-api-ruby/pull/1443) Add `ShopifyAPI::Utils::ShopValidator` with `sanitize_shop_domain` and `sanitize!`.
|
|
8
13
|
- [#1443](https://github.com/Shopify/shopify-api-ruby/pull/1443) Derive the target shop for `ShopifyAPI::Auth::TokenExchange.exchange_token` from the session token's `dest` claim. The `shop` argument is now deprecated and will be removed in the next major version.
|
data/Gemfile.lock
CHANGED
|
@@ -33,6 +33,8 @@ module ShopifyAPI
|
|
|
33
33
|
end
|
|
34
34
|
raise Errors::UnsupportedOauthError, "Cannot perform OAuth for private apps." if Context.private?
|
|
35
35
|
|
|
36
|
+
shop = Utils::ShopValidator.sanitize!(shop)
|
|
37
|
+
|
|
36
38
|
state = SecureRandom.alphanumeric(NONCE_LENGTH)
|
|
37
39
|
|
|
38
40
|
cookie = SessionCookie.new(value: state, expires: Time.now + 60)
|
data/lib/shopify_api/auth.rb
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "addressable/uri"
|
|
5
|
+
|
|
4
6
|
module ShopifyAPI
|
|
5
7
|
module Auth
|
|
6
8
|
extend T::Sig
|
|
7
9
|
|
|
10
|
+
# Hostname characters, plus an optional port. Anything else before the first "/" is rejected.
|
|
11
|
+
EMBEDDED_HOST_AUTHORITY_REGEX = T.let(/\A[a-zA-Z0-9._-]+(:\d{1,5})?\z/, Regexp)
|
|
12
|
+
|
|
13
|
+
# Printable ASCII only: no whitespace, control bytes or non-ASCII bytes.
|
|
14
|
+
EMBEDDED_HOST_ALLOWED_BYTES_REGEX = T.let(/\A[\x21-\x7e]+\z/n, Regexp)
|
|
15
|
+
private_constant :EMBEDDED_HOST_AUTHORITY_REGEX, :EMBEDDED_HOST_ALLOWED_BYTES_REGEX
|
|
16
|
+
|
|
8
17
|
class << self
|
|
9
18
|
extend T::Sig
|
|
10
19
|
|
|
@@ -19,8 +28,29 @@ module ShopifyAPI
|
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
decoded_host = Base64.decode64(host)
|
|
31
|
+
unless trusted_embedded_host?(decoded_host)
|
|
32
|
+
raise Errors::InvalidShopError, "host must decode to a trusted Shopify Admin host"
|
|
33
|
+
end
|
|
34
|
+
|
|
22
35
|
"https://#{decoded_host}/apps/#{Context.api_key}"
|
|
23
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
sig { params(decoded_host: String).returns(T::Boolean) }
|
|
41
|
+
def trusted_embedded_host?(decoded_host)
|
|
42
|
+
return false unless decoded_host.b.match?(EMBEDDED_HOST_ALLOWED_BYTES_REGEX)
|
|
43
|
+
|
|
44
|
+
authority = T.must(decoded_host.split("/", 2).first)
|
|
45
|
+
return false unless authority.match?(EMBEDDED_HOST_AUTHORITY_REGEX)
|
|
46
|
+
|
|
47
|
+
uri = Addressable::URI.parse("https://#{decoded_host}")
|
|
48
|
+
return false unless uri.userinfo.nil? && uri.authority == authority
|
|
49
|
+
|
|
50
|
+
!Utils::ShopValidator.sanitize_shop_domain(uri.to_s).nil?
|
|
51
|
+
rescue Addressable::URI::InvalidURIError
|
|
52
|
+
false
|
|
53
|
+
end
|
|
24
54
|
end
|
|
25
55
|
end
|
|
26
56
|
end
|
|
@@ -17,6 +17,10 @@ module ShopifyAPI
|
|
|
17
17
|
T::Array[String],
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
HOSTNAME_REGEX = T.let(/\A[a-z0-9_-]+(\.[a-z0-9_-]+)*\.?\z/, Regexp)
|
|
21
|
+
STORE_HANDLE_REGEX = T.let(/\A[a-z0-9][a-z0-9-]*\z/, Regexp)
|
|
22
|
+
private_constant :HOSTNAME_REGEX, :STORE_HANDLE_REGEX
|
|
23
|
+
|
|
20
24
|
class << self
|
|
21
25
|
extend T::Sig
|
|
22
26
|
|
|
@@ -29,6 +33,7 @@ module ShopifyAPI
|
|
|
29
33
|
def sanitize_shop_domain(shop_domain, myshopify_domain: nil)
|
|
30
34
|
uri = uri_from_shop_domain(shop_domain, myshopify_domain)
|
|
31
35
|
return nil if uri.nil? || uri.host.nil? || uri.host.empty?
|
|
36
|
+
return nil unless T.cast(uri.host, String).match?(HOSTNAME_REGEX)
|
|
32
37
|
|
|
33
38
|
trusted_domains(myshopify_domain).each do |trusted_domain|
|
|
34
39
|
host = T.cast(uri.host, String)
|
|
@@ -107,9 +112,13 @@ module ShopifyAPI
|
|
|
107
112
|
T.cast(uri.host, String).split(".").first == "admin"
|
|
108
113
|
end
|
|
109
114
|
|
|
110
|
-
sig { params(uri: Addressable::URI).returns(String) }
|
|
115
|
+
sig { params(uri: Addressable::URI).returns(T.nilable(String)) }
|
|
111
116
|
def myshopify_domain_from_unified_admin(uri)
|
|
112
|
-
|
|
117
|
+
segments = uri.path.to_s.split("/")
|
|
118
|
+
store_index = segments.index("store")
|
|
119
|
+
shop = store_index && segments[store_index + 1]
|
|
120
|
+
return unless shop&.match?(STORE_HANDLE_REGEX)
|
|
121
|
+
|
|
113
122
|
"#{shop}.myshopify.com"
|
|
114
123
|
end
|
|
115
124
|
end
|
data/lib/shopify_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shopify_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.3.
|
|
4
|
+
version: 16.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
@@ -1703,7 +1703,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1703
1703
|
- !ruby/object:Gem::Version
|
|
1704
1704
|
version: '0'
|
|
1705
1705
|
requirements: []
|
|
1706
|
-
rubygems_version: 4.0.
|
|
1706
|
+
rubygems_version: 4.0.22
|
|
1707
1707
|
specification_version: 4
|
|
1708
1708
|
summary: The gem for accessing the Shopify API
|
|
1709
1709
|
test_files: []
|