gsk_cache 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/gsk_cache.rb +175 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4e4eaba75a4c51cec1231c1e554101679c450c1736e9fd1d6849ff3d3d92b0ec
|
4
|
+
data.tar.gz: 48019eda48fb59c0efbef7f71b6e06e74c1ee7312e9e129386249222ba5d8ac9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d15feb37ef8bdfc36f4f4bd2c157af70214bfdc33c195726c7804f00c935a7bfc2b1354d973ec4ed745ee02611d486107f9999e5116f34363d51162647a23ef
|
7
|
+
data.tar.gz: 117c8510dd5978cce0ba8c00559a5d8b97348d761e6736875a0baf2c9e0f7bdf36649bc21edac574128ee4ce8941ac285ff959a619988a5eb3c4b4a4d959383e
|
data/lib/gsk_cache.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module GSKCache
|
5
|
+
@@key_updater_semaphore = Mutex.new
|
6
|
+
@@key_updater_thread = nil
|
7
|
+
|
8
|
+
##
|
9
|
+
# Keys used for signing in production
|
10
|
+
SIGNING_KEY_URL = 'https://payments.developers.google.com/paymentmethodtoken/keys.json'.freeze
|
11
|
+
|
12
|
+
##
|
13
|
+
# Keys used for signing in a testing environment
|
14
|
+
TEST_SIGNING_KEY_URL = 'https://payments.developers.google.com/paymentmethodtoken/test/keys.json'.freeze
|
15
|
+
|
16
|
+
MAX_CACHE_TIMEOUT = 31 * 24 * 3600 # One month
|
17
|
+
MIN_CACHE_TIMEOUT = 600 # 10 minutes
|
18
|
+
|
19
|
+
BOOT_WAIT_TIME_MAX = 8
|
20
|
+
|
21
|
+
HEADER_KEY = 'Cache-Control'.freeze
|
22
|
+
|
23
|
+
READ_TIMEOUT = 10
|
24
|
+
CONNECT_TIMEOUT = 10
|
25
|
+
|
26
|
+
UPDATER_BLOCK = proc do
|
27
|
+
loop do
|
28
|
+
begin
|
29
|
+
timeout = 0
|
30
|
+
|
31
|
+
conn = Excon.new(@@source, connect_timeout: @@connect_timeout, read_timeout: @@read_timeout)
|
32
|
+
resp = conn.get
|
33
|
+
|
34
|
+
raise 'Unable to update keys: ' + resp.data[:status_line] unless resp.status == 200
|
35
|
+
|
36
|
+
if resp.headers.key?(HEADER_KEY) && resp.headers[HEADER_KEY].is_a?(String)
|
37
|
+
cache_control = resp.headers[HEADER_KEY].split(/,\s*/)
|
38
|
+
h = cache_control.map { |x| /\Amax-age=(?<timeout>\d+)\z/ =~ x; timeout }.compact
|
39
|
+
timeout = h.first.to_i if h.length == 1
|
40
|
+
else
|
41
|
+
log(:warning, "Missing/malformed #{HEADER_KEY} header")
|
42
|
+
end
|
43
|
+
|
44
|
+
if timeout.nil? || !timeout.positive?
|
45
|
+
log(:warning, 'Cache timeout was not parsed, falling back to 1 day')
|
46
|
+
timeout = 86400
|
47
|
+
end
|
48
|
+
|
49
|
+
# Fallback to longer cache if less than 10 minutes
|
50
|
+
if timeout < MIN_CACHE_TIMEOUT
|
51
|
+
log(:warning, "Cache timeout less than 10 minutes (#{timeout}s), defaulting to #{MIN_CACHE_TIMEOUT / 60} minutes")
|
52
|
+
timeout = MIN_CACHE_TIMEOUT
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fallback to shorter cache if longer than 30 days
|
56
|
+
if timeout > MAX_CACHE_TIMEOUT
|
57
|
+
log(:warning, "Cache timeout more than a month (#{timeout}s), defaulting to #{MAX_CACHE_TIMEOUT / (24 * 3600)} days")
|
58
|
+
timeout = MAX_CACHE_TIMEOUT
|
59
|
+
end
|
60
|
+
|
61
|
+
Thread.current.thread_variable_set('keys', resp.body)
|
62
|
+
|
63
|
+
# Supposedly recommended by Tink library
|
64
|
+
sleep_time = timeout / 2
|
65
|
+
|
66
|
+
log(:info, "Updated Google signing keys. Sleeping for #{seconds_to_time(sleep_time)}")
|
67
|
+
|
68
|
+
sleep sleep_time
|
69
|
+
rescue Interrupt => e
|
70
|
+
# When interrupted
|
71
|
+
log(:fatal, 'Quitting: ' + e.message)
|
72
|
+
return
|
73
|
+
rescue => e
|
74
|
+
log(:error, "Exception updating Google signing keys: '#{e.message}' at #{e.backtrace}")
|
75
|
+
|
76
|
+
# Don't retry excessively.
|
77
|
+
sleep 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Start a thread that keeps the Google signing keys updated.
|
84
|
+
def self.start(logger: nil, source: nil, waittime: BOOT_WAIT_TIME_MAX)
|
85
|
+
@@key_updater_semaphore.synchronize do
|
86
|
+
# Another thread might have been waiting for on the mutex
|
87
|
+
break unless @@key_updater_thread.nil?
|
88
|
+
|
89
|
+
@@logger = logger
|
90
|
+
@@source = if source
|
91
|
+
source
|
92
|
+
elsif ENV['ENVIRONMENT'] == 'production'
|
93
|
+
SIGNING_KEY_URL
|
94
|
+
else
|
95
|
+
TEST_SIGNING_KEY_URL
|
96
|
+
end
|
97
|
+
@@read_timeout = READ_TIMEOUT
|
98
|
+
@@connect_timeout = CONNECT_TIMEOUT
|
99
|
+
|
100
|
+
new_thread = Thread.new(&UPDATER_BLOCK)
|
101
|
+
|
102
|
+
start_time = Time.now.to_i
|
103
|
+
|
104
|
+
while new_thread.thread_variable_get('keys').nil? && Time.now.to_i - start_time < waittime
|
105
|
+
sleep 0.2
|
106
|
+
end
|
107
|
+
# Body has now been set.
|
108
|
+
# Let other clients through.
|
109
|
+
@@key_updater_thread = new_thread
|
110
|
+
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.set_timeout(read_timeout: nil, connect_timeout: nil)
|
116
|
+
@@read_timeout = read_timeout if read_timeout
|
117
|
+
@@connect_timeout = connect_timeout if connect_timeout
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.signing_keys
|
121
|
+
start if @@key_updater_thread.nil?
|
122
|
+
|
123
|
+
@@key_updater_thread.thread_variable_get('keys')
|
124
|
+
end
|
125
|
+
|
126
|
+
# Required for specs
|
127
|
+
def self.terminate
|
128
|
+
@@key_updater_thread&.terminate
|
129
|
+
@@key_updater_thread = nil
|
130
|
+
end
|
131
|
+
|
132
|
+
class << self
|
133
|
+
private
|
134
|
+
|
135
|
+
def log(level, message)
|
136
|
+
return if @@logger.nil?
|
137
|
+
|
138
|
+
case level
|
139
|
+
when :info
|
140
|
+
@@logger.info(message)
|
141
|
+
when :warning
|
142
|
+
if @@logger.respond_to?(:warning)
|
143
|
+
@@logger.warning(message)
|
144
|
+
elsif @@logger.respond_to?(:warn)
|
145
|
+
@@logger.warn(message)
|
146
|
+
end
|
147
|
+
when :error
|
148
|
+
@@logger.error(message)
|
149
|
+
when :fatal
|
150
|
+
@@logger.fatal(message)
|
151
|
+
else
|
152
|
+
throw RuntimeError.new('Invalid log level')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def seconds_to_time(s)
|
157
|
+
days = (s / 86400).floor
|
158
|
+
s %= 86400
|
159
|
+
|
160
|
+
hours = (s / 3600).floor
|
161
|
+
s %= 3600
|
162
|
+
|
163
|
+
minutes = (s / 60).floor
|
164
|
+
s %= 60
|
165
|
+
|
166
|
+
out = ''
|
167
|
+
out += "#{days} days " if days.positive?
|
168
|
+
out += "#{hours} hours " if hours.positive?
|
169
|
+
out += "#{minutes} minutes " if minutes.positive?
|
170
|
+
out += "#{s} seconds" if s.positive?
|
171
|
+
|
172
|
+
out.rstrip
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gsk_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Clearhaus
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: excon
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
description:
|
42
|
+
email: hello@clearhaus.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/gsk_cache.rb
|
48
|
+
homepage: https://github.com/clearhaus/gsk_cache
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.7.7
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Fetches and caches Google Pay signing keys
|
72
|
+
test_files: []
|