Skip to content

Commit 20b80dd

Browse files
authored
fix: compare URI-decoded path params (#482)
1 parent f092241 commit 20b80dd

File tree

7 files changed

+78
-3
lines changed

7 files changed

+78
-3
lines changed

lib/graphiti.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def self.cache
190190
require "graphiti/query"
191191
require "graphiti/debugger"
192192
require "graphiti/util/cache_debug"
193+
require "graphiti/util/uri_decoder"
193194

194195
if defined?(ActiveRecord)
195196
require "graphiti/adapters/active_record"

lib/graphiti/adapters/null.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ def filter_boolean_eq(scope, attribute, value)
168168
scope
169169
end
170170

171+
def filter_uuid_eq(scope, attribute, value)
172+
scope
173+
end
174+
175+
def filter_uuid_not_eq(scope, attribute, value)
176+
scope
177+
end
178+
171179
def base_scope(model)
172180
{}
173181
end

lib/graphiti/configuration.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Configuration
1818
attr_accessor :before_sideload
1919

2020
attr_reader :debug, :debug_models
21+
attr_reader :uri_decoder
2122

2223
attr_writer :schema_path
2324
attr_writer :cache_rendering
@@ -37,6 +38,8 @@ def initialize
3738
self.debug = ENV.fetch("GRAPHITI_DEBUG", true)
3839
self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false)
3940

41+
@uri_decoder = infer_uri_decoder
42+
4043
# FIXME: Don't duplicate graphiti-rails efforts
4144
if defined?(::Rails.root) && (root = ::Rails.root)
4245
config_file = root.join(".graphiticfg.yml")
@@ -85,6 +88,33 @@ def with_option(key, value)
8588
ensure
8689
send(:"#{key}=", original)
8790
end
91+
92+
def uri_decoder=(decoder)
93+
unless decoder.respond_to?(:call)
94+
raise "uri_decoder must respond to `call`."
95+
end
96+
97+
@uri_decoder = decoder
98+
end
99+
100+
private
101+
102+
def infer_uri_decoder
103+
if defined?(::ActionDispatch::Journey::Router::Utils)
104+
# available in all supported versions of Rails.
105+
# This method should be preferred for comparing URI path segments
106+
# to params, as it is the exact decoder used in the Rails router.
107+
@uri_decoder = ::ActionDispatch::Journey::Router::Utils.method(:unescape_uri)
108+
elsif URI.respond_to?(:decode_uri_component)
109+
# available in Ruby >= 3.2
110+
@uri_decoder = URI.method(:decode_uri_component)
111+
end
112+
rescue => e
113+
Kernel.warn("Error inferring Graphiti uri_decoder: #{e}")
114+
ensure
115+
# fallback
116+
@uri_decoder ||= :itself.to_proc
117+
end
88118
end
89119

90120
msg = "Use graphiti-rails's `config.graphiti.respond_to_formats`"

lib/graphiti/resource/links.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@ def allow_request?(request_path, params, action)
8181
endpoints.any? do |e|
8282
has_id = params[:id] || params[:data].try(:[], :id)
8383
path = request_path
84-
if [:update, :show, :destroy].include?(context_namespace) && has_id
84+
if [:update, :show, :destroy].include?(action) && has_id
8585
path = request_path.split("/")
86-
path.pop if path.last == has_id.to_s
86+
path.pop if Graphiti::Util::UriDecoder.decode_uri(path.last) == has_id.to_s
8787
path = path.join("/")
8888
end
89-
e[:full_path].to_s == path && e[:actions].include?(context_namespace)
89+
e[:full_path].to_s == path && e[:actions].include?(action)
9090
end
9191
end
9292

lib/graphiti/util/uri_decoder.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Graphiti
2+
module Util
3+
module UriDecoder
4+
def self.decode_uri(uri)
5+
Graphiti.config.uri_decoder.call(uri)
6+
end
7+
end
8+
end
9+
end

spec/resource_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,23 @@ def self.name
12381238
end
12391239
end
12401240
end
1241+
1242+
context "with a url-encoded path param" do
1243+
before do
1244+
request.env["PATH_INFO"] += "/123%3B456"
1245+
1246+
klass.instance_exec do
1247+
attribute :id, :uuid
1248+
end
1249+
end
1250+
1251+
it "works" do
1252+
klass
1253+
Graphiti.with_context ctx, :show do
1254+
expect { klass.find(id: "123;456") }.to_not raise_error
1255+
end
1256+
end
1257+
end
12411258
end
12421259

12431260
context "and the request matches a secondary endpoint" do

spec/spec_helper.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,13 @@ def check!
6565
database: ":memory:"
6666
Dir[File.dirname(__FILE__) + "/fixtures/**/*.rb"].sort.each { |f| require f }
6767
end
68+
69+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2.0")
70+
unless defined?(::ActionDispatch::Journey)
71+
require "uri"
72+
# NOTE: `decode_www_form_component` isn't an ideal default for production,
73+
# because it varies slightly compared to typical uri parameterization,
74+
# but it will allow tests to pass in non-rails contexts.
75+
Graphiti.config.uri_decoder = URI.method(:decode_www_form_component)
76+
end
77+
end

0 commit comments

Comments
 (0)