Skip to content

Commit 60f6988

Browse files
authored
Ensure JSON Key Path is an Actual File or IO Object (#134)
* Prevent Opening JSON Key Path unless It's an IO Object * Fix linting issues * Update checking for a valid credentials paths and filename * Rename open_json_key_path to valid_json_key_path? * Change credentials_error_msg method to a more descriptive name
1 parent ec489fa commit 60f6988

2 files changed

Lines changed: 108 additions & 7 deletions

File tree

lib/fcm.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
require "googleauth"
55

66
class FCM
7+
class InvalidCredentialError < StandardError; end
8+
79
BASE_URI = "https://fcm.googleapis.com"
810
BASE_URI_V1 = "https://fcm.googleapis.com/v1/projects/"
911
DEFAULT_TIMEOUT = 30
@@ -291,11 +293,31 @@ def jwt_token
291293
token["access_token"]
292294
end
293295

296+
def raise_credentials_error(param)
297+
error_msg = 'credentials must be an IO-like ' \
298+
'object or path. You passed'
299+
300+
param_klass = param.nil? ? 'nil' : "a #{param.class.name}"
301+
error_msg += " #{param_klass}."
302+
raise InvalidCredentialError, error_msg
303+
end
304+
305+
def valid_json_key_path?(path)
306+
valid_io_object = path.respond_to?(:open)
307+
return true if valid_io_object && File.file?(path)
308+
309+
max_path_len = 1024
310+
valid_path = path.is_a?(String) && path.length <= max_path_len
311+
valid_path && File.file?(path)
312+
end
313+
294314
def json_key
295315
@json_key ||= if @json_key_path.respond_to?(:read)
296316
@json_key_path
297-
else
317+
elsif valid_json_key_path?(@json_key_path)
298318
File.open(@json_key_path)
319+
else
320+
raise_credentials_error(@json_key_path)
299321
end
300322
end
301323
end

spec/fcm_spec.rb

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "spec_helper"
2+
require 'tempfile'
23

34
describe FCM do
45
let(:project_name) { 'test-project' }
@@ -13,27 +14,105 @@
1314
}
1415
end
1516

17+
let(:client_email) do
18+
'83315528762cf7e0-7bbcc3aad87e0083391bc7f234d487' \
19+
'c8@developer.gserviceaccount.com'
20+
end
21+
22+
let(:client_x509_cert_url) do
23+
'https://www.googleapis.com/robot/v1/metadata/x509/' \
24+
'fd6b61037dd2bb8585527679" + "-7bbcc3aad87e0083391b' \
25+
'c7f234d487c8%40developer.gserviceaccount.com'
26+
end
27+
28+
let(:large_file_name) do
29+
Array.new(1021) { 'a' }.join('') + '.txt'
30+
end
31+
32+
let(:creds_error) do
33+
FCM::InvalidCredentialError
34+
end
35+
36+
let(:json_credentials) do
37+
{
38+
"type": 'service_account',
39+
"project_id": 'example',
40+
"private_key_id": 'c09c4593eee53707ca9f4208fbd6fe72b29fc7ab',
41+
"private_key": OpenSSL::PKey::RSA.new(2048).to_s,
42+
"client_email": client_email,
43+
"client_id": 'acedc3c0a63b3562376386f0.apps.googleusercontent.com',
44+
"auth_uri": 'https://accounts.google.com/o/oauth2/auth',
45+
"token_uri": 'https://oauth2.googleapis.com/token',
46+
"auth_provider_x509_cert_url": 'https://www.googleapis.com/oauth2/v1/certs',
47+
"client_x509_cert_url": client_x509_cert_url,
48+
"universe_domain": 'googleapis.com'
49+
}.to_json
50+
end
51+
1652
before do
1753
allow(client).to receive(:json_key)
1854

1955
# Mock the Google::Auth::ServiceAccountCredentials
20-
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds).
21-
and_return(double(fetch_access_token!: { 'access_token' => mock_token }))
56+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds)
57+
.and_return(double(fetch_access_token!: { 'access_token' => mock_token }))
2258
end
2359

24-
it "should initialize" do
60+
it 'should initialize' do
2561
expect { client }.not_to raise_error
2662
end
2763

2864
describe "credentials path" do
29-
it "can be a path to a file" do
65+
it 'can be a path to a file' do
3066
fcm = FCM.new("README.md")
3167
expect(fcm.__send__(:json_key).class).to eq(File)
3268
end
3369

34-
it "can be an IO object" do
35-
fcm = FCM.new(StringIO.new("hey"))
70+
it 'raises an error when passed a large path' do
71+
expect do
72+
FCM.new(large_file_name).__send__(:json_key)
73+
end.to raise_error(creds_error)
74+
end
75+
76+
it 'can be an IO object' do
77+
fcm = FCM.new(StringIO.new('hey'))
3678
expect(fcm.__send__(:json_key).class).to eq(StringIO)
79+
80+
temp_file = Tempfile.new('hello_world.json')
81+
temp_file.write(json_credentials)
82+
fcm_with_temp_file = FCM.new(temp_file)
83+
84+
expect do
85+
fcm_with_temp_file
86+
end.not_to raise_error
87+
temp_file.close
88+
temp_file.unlink
89+
end
90+
91+
it 'raises an error when passed a non IO-like object' do
92+
expect do
93+
FCM.new(nil, '', {}).__send__(:json_key)
94+
end.to raise_error(creds_error, 'credentials must be' \
95+
' an IO-like object or path. You passed nil.')
96+
97+
expect do
98+
FCM.new(json_credentials, '', {}).__send__(:json_key)
99+
end.to raise_error(creds_error, 'credentials must be' \
100+
' an IO-like object or path. You passed a String.')
101+
102+
expect do
103+
FCM.new({}, '', {}).__send__(:json_key)
104+
end.to raise_error(creds_error, 'credentials must be' \
105+
' an IO-like object or path. You passed a Hash.')
106+
end
107+
108+
it 'raises an error when passed a non-existent credentials file path' do
109+
fcm = FCM.new('spec/fake_credentials.json', '', {})
110+
expect { fcm.__send__(:json_key) }.to raise_error(creds_error)
111+
end
112+
113+
it 'raises an error when passed a string of a file that does not exist' do
114+
fcm = FCM.new('example.txt', '', {})
115+
expect { fcm.__send__(:json_key) }.to raise_error(creds_error)
37116
end
38117
end
39118

0 commit comments

Comments
 (0)