From 96a0bde0aae8ae8fdea158f9b7849cfc8999459b Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 10 Mar 2021 11:48:58 +0000 Subject: [PATCH] FEATURE: Allow JSON paths with literal dots in the keys (#33) This is achieved by surrounding a key with double quotes, or by escaping the dot character with a backslash. For example, given the JSON ``` { "www.example.com/uid": "myuid" } ``` Previously, there was no way to access this value. The dots would make the parser try to access `json["www"]["example"]["com/uid"]`. Now, this value can be accessed by using a `oauth2_json_user_id_path` like: ``` www\.example\.com/uid ``` or alternatively: ``` "www.example.com/uid" ``` --- plugin.rb | 24 +++++++++++++++++++++++- spec/plugin_spec.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 262ea93..a89f50e 100644 --- a/plugin.rb +++ b/plugin.rb @@ -166,12 +166,34 @@ class ::OAuth2BasicAuthenticator < Auth::ManagedAuthenticator if path.present? #this.[].that is the same as this.that, allows for both this[0].that and this.[0].that path styles path = path.gsub(".[].", ".").gsub(".[", "[") - segments = path.split('.') + segments = parse_segments(path) val = walk_path(user_json, segments) result[prop] = val if val.present? end end + def parse_segments(path) + segments = [+""] + quoted = false + escaped = false + + path.split("").each do |char| + next_char_escaped = false + if !escaped && (char == '"') + quoted = !quoted + elsif !escaped && !quoted && (char == '.') + segments.append +"" + elsif !escaped && (char == '\\') + next_char_escaped = true + else + segments.last << char + end + escaped = next_char_escaped + end + + segments + end + def log(info) Rails.logger.warn("OAuth2 Debugging: #{info}") if SiteSetting.oauth2_debug_auth end diff --git a/spec/plugin_spec.rb b/spec/plugin_spec.rb index 664b7b2..a2501a7 100644 --- a/spec/plugin_spec.rb +++ b/spec/plugin_spec.rb @@ -177,6 +177,38 @@ describe OAuth2BasicAuthenticator do expect(result).to eq "test@example.com" end + it 'allows keys containing dots, if wrapped in quotes' do + authenticator = OAuth2BasicAuthenticator.new + json_string = '{"www.example.com/uid": "myuid"}' + SiteSetting.oauth2_json_user_id_path = '"www.example.com/uid"' + result = authenticator.json_walk({}, JSON.parse(json_string), :user_id) + + expect(result).to eq "myuid" + end + + it 'allows keys containing dots, if escaped' do + authenticator = OAuth2BasicAuthenticator.new + json_string = '{"www.example.com/uid": "myuid"}' + SiteSetting.oauth2_json_user_id_path = 'www\.example\.com/uid' + result = authenticator.json_walk({}, JSON.parse(json_string), :user_id) + + expect(result).to eq "myuid" + end + + it 'allows keys containing literal backslashes, if escaped' do + authenticator = OAuth2BasicAuthenticator.new + # This 'single quoted heredoc' syntax means we don't have to escape backslashes in Ruby + # What you see is exactly what the user would enter in the site settings + json_string = <<~'_'.chomp + {"www.example.com/uid\\": "myuid"} + _ + SiteSetting.oauth2_json_user_id_path = <<~'_'.chomp + www\.example\.com/uid\\ + _ + result = authenticator.json_walk({}, JSON.parse(json_string), :user_id) + expect(result).to eq "myuid" + end + it 'can walk json that contains an array' do authenticator = OAuth2BasicAuthenticator.new json_string = '{"email":"test@example.com","identities":[{"user_id":"123456789","provider":"auth0","isSocial":false}]}'